home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-11-18 | 157.6 KB | 4,098 lines | [TEXT/MPS ] |
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- #
- # Apple Macintosh Developer Technical Support
- #
- # MultiFinder-Aware SoundApp Application
- #
- # SoundApp
- #
- # SoundApp.p - MPW 3.0/3.1 Pascal Source
- #
- # Jim Reekes - Macintosh Developer Technical Support
- # Copyright © 1989-1990 Apple Computer, Inc.
- # All rights reserved.
- #
- # Versions:
- # 1.03 May, 1990
- # 1.04 Sept, 1990
- # 1.1b1 Nov, 1990 MPW 3.2 update
- #
- # Components:
- # SoundApp.make May, 1990 MPW build script
- # SoundApp.p May, 1990 Pascal source code
- # SoundApp.r May, 1990 Rez source code
- # SoundAppSnds.r May, 1990 Rez source code
- # SoundUnit.p May, 1990 Pascal source code
- # Utilities.p Nov, 1990 helpful routines from MacDTS
- #
- # Version comments
- #
- # 1.04: This was an update to support the Utilities Unit that MacDTS
- # developed. Some of it was routines formally created for SoundApp,
- # with additional ones coming from other sources. This helps to reduce
- # the "destractive" code from the samples and makes them easier to maintain.
- #
- # 1.1: This is the "new" SoundApp which adds some new features.
- # • Some knowledge of the new Sound Manager is present
- # in areas that were work-arounds for old Sound Manager bugs
- # • Recording of sounds is now possible with the Sound Input Manager
- # • The document window will keep the Record button hidden if
- # the Sound Inpute Manager is not available for recording,
- # otherwise it will expand the window size to show this button.
- # • Supports copy and pasting of sound resources
- # • Conversion to MPW 3.2 was established (with some amount of pain)
- # • SoundApp now has its own documents
- # • Supports launching by open documents from Finder
- # • Added color icons for the System 7.0 desktop
- # • Added the new Sound Manager error strings
- # • DoErrorSound tests for safe beeps
- # • Made Apple Legal happy by getting rid of the xxxxCmd
- # and changed it to the freqDurationCmd. Also got rid of
- # any other use of the work "xxxx" in a musical context
- #
- #
- # Formatting was done with FONT = Monaco, SIZE = 9, TABS = 3
- #
- # many thanks to: Bo3b Johnson, Mark Bennett, Andy Shebanow, Keith
- # Rollin, Chris Derossi, Pete Helme, Darin Adler, and my co-workers that
- # sat near me while I was making lots of noise testing this application.
- #
- # To the reader,
- #
- # SoundApp.p is a sample application source file for demonstrating
- # the Sound Manager. It requires the use of the SoundUnit to handle
- # all of the sound routines. This portion of the source code handles the
- # application’s management of memory, errors, user interface, etc..
- # The read me notes are here in the source code because people tend to
- # not read them otherwise.
- #
- # SoundApp is a demonstration of the Sound Manager released in System
- # 6.0.x. SoundApp is also a example of how to write a Macintosh
- # application that performs good user interface and proper error
- # handling. Believe it or not, but the Sound Manager portion was the
- # easiest part for me to write. It was all the user interface and error
- # handling that was the most difficult. One thing became very clear to
- # me in the course of writing this application. The following axiom is
- # one of the few great truths in the universe.
- #
- # If you write a Macintosh application without MacApp, you’re working
- # too hard.
- #
- # Throughout the development of SoundApp I would run into typical
- # situations that make programming a Macintosh too hard. When this
- # happened I asked myself “what would MacApp do?” and that was followed
- # by the thought “then why am I writing code that is already out there
- # for me to use?” I started out intending on writing a very simple
- # application that anyone could read, and understand the Sound Manager.
- # I felt this meant not requiring the person to read Object Oriented
- # Pascal. I accomplished part of my goal. People should be able to
- # learn how to use the Sound Manager (in its present condition), but it
- # didn’t turn out to be such a simple application as I had hoped for.
- #
- # I have put a large amount of comments into the code. This is
- # something I’m really picky about. People do not comment their code
- # enough. Each procedure has a paragraph that should explain what to
- # expect that routine to do, and how it goes about doing it. There are
- # some bigger issues that I will put into the release notes below.
- # There are some things that make the Macintosh harder to develop for
- # than it should be. Simple things should be simple. Too many things
- # on the Mac that should be simple are not. Maybe someday these things
- # will be fixed.
- #
- # • GetFontInfo requires a port set to the font in question. If I
- # wanted to find the height of the System Font, I had to first set the
- # current port to the WindowMgr. I could have used my own window, but
- # what if I needed the font info before I had a window available?
- # • The toolbox blows chunks when your heap gets “too low.” I believe
- # this magical number is between 32k and zero. The odds of blowing
- # chunks increase logarithmically as one approaches 0 free bytes.
- # • The Dialog Manager is not a free lunch and in fact will cost you
- # plenty.
- # • There’s no safe way to determine how much memory opening a
- # resource file will take away from your heap.
- # • There’s no way to determine how safe it is opening a resource that
- # could be shared by other applications, especially on a local volume.
- # • The Resource Manager doesn’t always set ResErr.
- # • The Sound Manager returns even less errors.
- # • The List Manager returns no errors.
- # • Writing a staggering routine for new windows encompasses a number
- # of difficulties. How does one find the height of a window’s title
- # region before the window is visible?
- #
- # Am I just a complainer? Do I have a bad attitude? Probably, but
- # I’m just trying to point out the above areas make the Mac programming
- # experience difficult. These are areas that get developers into
- # trouble. These areas need a sign in front of them that says,
- # “Danger!” These are areas that developers get into and then write to
- # MacDTS for help.
- #
- # Notation Conventions
- # --------------------
- # All Pascal reserved words are in upper case. All global variables
- # begin with a lower case “g”. All constants begin with “k” except for
- # those noted here. Resource IDs begin with “r”. Menu IDs being with
- # “m” and items with an “i”. Resource strings begin with “s”.
- #
- # Human Interface
- # ---------------
- # This is the most important and about the most difficult aspect of
- # programming on the Macintosh. In the development of SoundApp I gave much
- # thought to the human interface issues. In fact, in talking with the Human
- # Interface Group additions to the existing guidelines were made. The
- # method of window stacking used here was a new one. This was documented in
- # a Human Interface Tech Note. I even made one compromise (hard to
- # believe!) suggested by the Human Interface Group. I originally had the
- # buttons and the list in my choice of font and size. They felt that
- # buttons should be in the System font and the list should also be the same.
- # I liked my font choice better, but the group had a point that I really
- # couldn’t argue with. That was, “If there isn’t a compelling reason to
- # change something standard, then don’t change it.” Buttons on a Macintosh
- # typically appear in the System font. Changing the font, just because I
- # wanted to, was considered gratuitous. Standard File is in the System font
- # and it also contains a list and buttons. Since my window are very similar
- # to that dialog, I’m using the System Font.
- #
- # SoundApp is never modal unless an error occurs and I need to show and
- # alert. Controls are inactivated for inactive windows. The default button
- # is given the proper outline, and this outline disappears when the window
- # is deactivated. Keyboard equivalents for the buttons cause the button to
- # appear as if it had been clicked in. The check box in the Standard File
- # dialog remembers the user’s last setting. The about box is only
- # semi-modal. It will allow the user access to switch to another application.
- # The status window under some circumstances was found to disappear too quickly,
- # so a built in delay was added. Windows are centered or stacked according
- # to the Human Interface Guidelines. The sound level isn’t adjusted by the
- # application, and instead the users is informed of the current level and
- # told how it can be adjusted.
- #
- # The About box
- # -------------
- # It’s rad. Has a color icon, shows the 'vers' resource, goes Moof!™. It
- # also demonstrates how to handle a modal window without the Dialog Manager.
- # This technique can be use for any window, including dialog windows. The
- # dialog’s update routine would call UpdateDialog. The really new point to
- # notice is this window is modal but ONLY within the application’s layer.
- # While running under MultiFinder, the user can switch to other
- # applications. While the About window is present, it is the only window
- # belonging to the application that accepts user actions.
- #
- # Memory Management
- # ----------------
- # This has to be the most difficult portion of a Mac application to
- # write. This along with the user interface. I spent too many nights
- # chasing down crashes while running the application under low memory.
- # I found unpleasant surprises while doing this. The Sound Manager
- # doesn’t check for NIL pointers. OpenResFile may take large portions
- # of my heap away. The toolbox seems to need at least 32k of free space
- # in the heap of my Mac II running color.
- #
- # I wrote a simple grow zone procedure that will dump a reserve memory
- # block. This is only considered for use in an emergency. I never rely
- # on using it directly. If the reserve has been released, I will not
- # continue an operation such as playing a sound or showing the status
- # window until it is regained. Grow zones should not be considered a
- # solution to memory management. They can be used to augment your
- # overall memory management scheme.
- #
- # Error Checking
- # --------------
- # Lots and lots and lots of it. I could even do more. Programmers
- # need to do more of this. The Sound Manager will crash when
- # encountering a NIL pointer. My application should never crash. If
- # you can find a way to crash this application, then I want to hear
- # about it. Using a bogus 'snd ' resources doesn’t count and I’ve found
- # many of those. Writing proper error checking into the code during
- # development really helped. Always handle errors, and pass along the
- # error. I will let the first error encountered to be passed all the
- # way up to the caller and eventually my error dialog will show up for
- # the user.
- #
- # SetPort Strategy
- # ----------------
- # Any routine that needs to use Quickdraw will set the port. I do not
- # believe that it should also be responsible for restoring the port back
- # to what it may have been before the routine was called. So, you’ll
- # find there is an absence of the GetPort, SetPort, do my thing, and
- # then SetPort again. Instead I SetPort and do my thing. The Mac often
- # is setting the port unnecessarily.
- #
- # Strings
- # -------
- # All of my strings are resources. There are no strings that appear
- # within the code. This helps memory management and allows me to adjust
- # the application to international systems without compiling any code.
- # I could simply use ResEdit, or some other tool, to localize this
- # application. I find it is just as easy to run Rez again than
- # attempting to use ResEdit. Besides, after editing with ResEdit I want
- # the source for that and would have to run DeRez which isn’t nearly as
- # clean as my original source files.
- #
- # Window Stacking
- # ---------------
- # I hate applications that will open a new document that covers up an
- # existing document, unless the new document covers the entire screen.
- # So, my application’s documents have a small window size. I wanted to
- # open new windows that would not cover up older ones. This is nice for
- # the user, since they will not have to move windows just to get at
- # other documents. ResEdit will stagger new windows off of the
- # frontmost window but I find that this isn’t the best approach. It
- # will still cover up other windows and I also don’t like windows that
- # will open half way between two monitors. I wanted a better approach:
- # one that would always stagger new windows and not cover up older ones.
- #
- # When I want to center a window, I need to know its entire rectangle
- # size. The rub is that I cannot determine its size until I show it
- # because I only know about the window’s boundsRect. This does not
- # include the area that contains the title bar. That’s the strucRgn,
- # which is an empty region for an invisible window. I could do what
- # MacApp does, but if I have to do another thing that MacApp already
- # does I’ll give up and stick with MacApp. I ended up writing a routine
- # that takes a guess at the height of the window’s title bar. This is
- # another thing that was harder than it should have been.
- #
- # Dialog Manager (and some of the reasons I don’t like it)
- # --------------------------------------------------------
- # My first approach was to use modeless dialogs for document windows,
- # thinking that I could write an application that would demonstrate how to
- # deal with them and all of their idiosyncrasies. Not long into the
- # development cycle it became obvious to me that I was fighting something
- # that contained more disadvantages than advantages. I removed all the
- # dialog code and only used standard windows and controls the old fashion
- # way. In the case of the About window, which is semi-modal, I have a test
- # that will return TRUE for a window that should be treated as modal. This
- # allows my window to be handled by my standard event handlers and I don’t
- # have to write dialog filters. There are some things that do not get
- # handled properly while calling ModalDialog. ModalDialog ignores disk
- # insert events. The activate or update events do not get handled for
- # background windows. Using a modeless dialog fails with MultiFinder if
- # switching takes place while the dialog is the frontmost window. The
- # problem is that DialogSelect ignores and removes the suspend/resume event.
- # Another advantage to all this is that drawing was much faster.
- #
- # As an example of some of the problems with ModalDialog and the activate
- # event. Try this with the Finder. Open a window and choose “View by name.”
- # Then select a few names with the shift key and resize the window so the
- # vertical scroll bar is visible. Move this window to one edge of the
- # screen or a second monitor. Now choose “Set Startup.” This is a modal
- # dialog. If you look at the Finder window with the selected files, you’ll
- # notice that the scrollbar and the text are still highlited. This is not
- # the proper user interface. This is because the deactivate routines are
- # not called while in ModalDialog. You can even find this problem with
- # SoundApp. On deactivate events I will change my controls to the inactive
- # state. If you place the buttons to the side of the screen and then bring
- # up the standard file dialog, you’ll notice that the buttons don’t change
- # properly. ModalDialog also prevents the application from updating
- # background windows too. To solve this a dialog filter procedure is
- # required. In most cases, this filter would be as complex and the event
- # loop. It would also make it necessary to call your event routines from
- # outside of the normal event loop. All on this isn’t worth the effort.
- #
- # You can see how this does not happen while using this application’s
- # About window. Select an item in the document window and choose “Play
- # Melody.” This will leave the status window on screen so that you can
- # drag it to cover the document window. Now select “About SoundApp” to
- # bring up the about window. This causes the status window to close,
- # which uncovers the document window leaving an invalid area. The
- # document window gets an activate event, then the About window appears.
- # Then the document window is properly deactivated and updated. Yeah,
- # just like it should happen.
- #
- # So, the tradeoff was that I didn’t have to work around all the
- # strange things the Dialog Manager does such as running a secondary
- # event loop, and requiring me to have userItems or filterProcs. This
- # made the code smaller, more readable, and faster. I think I will
- # avoid the Dialog Manger from now one unless I’m using a very simple
- # dialog. The about window of this application proved too much for the
- # Dialog Manager.
- #
- # One thing dialogs are good for is running ResEdit and laying out the
- # dialog. To help position controls, I used a DLOG resource of the same
- # size as my WIND resource. The DITL of this dialog contains the
- # positions I wanted for my CNTL resources. This helped me to look at
- # where I could expect my buttons to show up. This is one of the main
- # reasons people think they need the Dialog Manager, because ResEdit
- # makes it easy to build dialogs. ResEdit alone has contributed to
- # nearly all of the Dialog Manager abuse in the world today.
- #
- # I used a Rect resource for positioning the list rectangle of the
- # document windows. These windows look very much like a modeless
- # dialog. (They used to be, but that presented to many problems.) The
- # About window is also a standard window, but shown modally. Just like
- # ModalDialog, but my modal window does allow switching under
- # MultiFinder. You can change the window to a dBoxProc and then
- # MultiFinder will not switch while this is the active window. To help
- # with the layout of the about window, I position the text within it
- # based on the size of the window. The status window does this too.
- # These two things, the Rect resource and text based on the size of the
- # window, help when changing the text. If the new text doesn’t fit,
- # then resize the window’s resource. I used some trick with Rez to help
- # layout my window contents. Refer to the SoundApp.r sources.
- #
- # I’ve read and understood Tech Note #203, and have learned how to
- # apply it. Bo3b Johnson is a smart guy, and developers should trust
- # his opinions.
- #
- # List Manager
- # ------------
- # It’s very easy to be tempted by this part of the toolbox, along with
- # the Dialog Manager. The List Manager is a slow beast at times. It
- # also has some problems with “doing the right thing.” I’ve found that
- # the list will not be updated properly when the user clicks in a cell
- # that is out of bounds. LClick will return TRUE with a cell that
- # doesn’t exists. LActivate will erase the scrollbars instead of
- # highlighting the properly. Finally, the List Manager does not return
- # errors. How would a person know if LSetCell worked?
- #
- # I’ve read and understood Tech Note #203, and have learned how to
- # apply it.
- #
- # Resource Manager
- # ----------------
- # I test all the handles being returned from the Resource Manager
- # before using them, and if I get a NIL then I look at ResError.
- # ResError sometimes lies and returns noErr and a NIL handle. ResError
- # is usually good for getting an error code AFTER you’ve already found
- # an error.
- #
- # Opening a resource file that is already open by another application
- # is dangerous. The Resource Manager will not tell you when you’ve done
- # this. There needs to be a OpenRFPerm that will return permission
- # errors such as resFileBusyErr. Refer to Tech Note #185.
- #
- # When I or the Toolbox needs to get at one of my resources,
- # CurResFile must be set to my application. Also, look out for one
- # particularly nasty situation when switching resource files. If the
- # segment loader goes for a CODE segment, it better be from our resource
- # file! The idea here is, in case you didn’t get it already, always
- # have the current resource file be set to the application. If a
- # resource is needed from another file, switch momentarily to get the
- # resource and immediately restore the current resource file to the
- # application. I take an added measure of defense and whenever I need a
- # resource I use the Get1Resource calls. These will only search the
- # current resource file.
- #
- # Strategies For Sound
- # --------------------
- # All of the Sound Manager code is contained in the SoundUnit.p. This
- # unit was written to be general purpose, providing useful routines for
- # other applications. Lots of error checking is performed. I’ve also
- # extended the support for SndPlay and made it really asynchronous.
- # I’ve demonstrated most of the abilities the present Sound Manager has
- # to offer. I will have to revise the SoundAppUnit to include any new
- # features (e.g., multi channel support) when the next Sound Manager is
- # released.
- #
- # I allocate my own memory to be used as sound channels. I allocate
- # these pointers early in the application’s startup time to avoid memory
- # fragmentation. These channels are of the standard size (holding 128
- # commands) but I’ve extended the structure to include my own
- # information. When I create a new sound channel, I pass it a pointer
- # to this memory. This will link in the 'snth' resource and hardware to
- # my channel. When I dispose of the channel, the Sound Manager will
- # purge this resource and disconnect me from the hardware. When adding
- # the 'snth' resource, the Sound Manager will allocate a pointer into
- # the application’s heap instead of the system’s. This is a modifier
- # stub used by the 'snth'. This could cause some problems with memory
- # management. I create and dispose of all my channels as soon as
- # possible, and this doesn’t cause me problems.
- #
- # I keep track of which document is playing a sound, along with a
- # global of when the application is playing sound. I needed to keep
- # track of which document is playing because if the user disposes of
- # that document, I will have to stop playing the sound contained in it
- # since the user wants to dispose of that data. I keep track of when
- # the application is playing sound in a global. This is only used by
- # the routine that calculates the sleep time for WaitNextEvent.
- #
- # I came up with a pretty sick music notational system using Rez.
- # Refer to the notes in the SoundAppSnds.r file. If you’ve just
- # finished a meal, wait four hours before reading.
- #
- # The SoundUnit handles all of the Sound Manager code entirely. This
- # eliminates any and all references to the Sound Manager from the
- # application. The SoundUnit will return any error encountered while
- # calling the Sound Manager, and does some extra error checking the
- # Sound Manager doesn’t do.
- #
- # The portion of the application that uses the wave table synthesizer
- # is more complex than the other two. I wanted to include an example
- # channel modifier for use in the wave table channels. This would have
- # been a transpositional modifier that would take a given freqDurationCmd and
- # transpose it by some amount. This would be nice for the routine that
- # plays a scale, by allowing the other three channels to be playing the
- # same scale but at a different interval. Unfortunately, I found that
- # the Sound Manager has bugs using a modifier, at least with the wave
- # table synths, and could not use them.
- #
- # I’ve created a few wave table sounds and keep them in a 'snd '
- # resource. This allows me to change the sound of the wave table
- # channels and not change any of the code. Creating wave table data is
- # complicated. The example sounds I’ve included are samples I’ve taken
- # from various sources. I’ve cleaned them up quit a bit. This was to
- # set loop points, try and reduce clicks, correct the sample rates, and
- # base frequencies. This is also a complicated task. Maybe I should document
- # these techniques.
- #
- # Jim Reekes E.O., Macintosh Developer Technical Support
- # Tuesday, January 30, 1990 1:01 PM
- #
- #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- PROGRAM SoundApp;
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- USES
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- Types, QuickDraw, Traps, Events, Controls, Windows, TextEdit, Dialogs,
- Fonts, Lists, Menus, Resources, Scrap, ToolUtils, Files, SegLoad,
- Packages,
- DiskInit, LowMem, Script, Errors, CursorCtl, Sound, SoundInput,
- SoundUnit, Devices, StandardFile;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- CONST
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- kSysEnvironsVersion = 1; {Version of the SysEnvRec I understand.}
- kSystem602 = $0602; {SysEnvRec value for System version 6.0.2}
- kPollingSleepTime = 60; {MultiFinder’s sleep while sound is playing}
-
- kNumberOfMasters = 3; {number of master pointer blocks we expect}
- kSizeOfReserve = 32 * 1024; {size of reserve memory for grow zone proc}
- kMinSpace = 32 * 1024; {minimum available memory I allow in heap}
- kMemForSndDoc = 20 * 1024; {minimal amount of memory needed by document}
-
- rAppSignature = 'SAPP'; {applicaiton’s OS signature}
- rSndAppDocType = 'sDoc'; {document's file type}
-
- kScrollBarAdjust = 15; {the width of the scrollbar in the list}
- kListFrameInset = -1; {inset rectangle adjustment for list frame}
- kMsgInset = 8; {inset rectangle adjustment for messages}
-
- kSFTopLeft = $00280032; {40,50 is topLeft for Std File dialog}
-
- kCntlActivate = 0; {enabled control’s hilite state}
- kCntlDeactivate = 255; {disabled control’s hilite state}
- kSelect = 1; {select the control}
- kDeselect = 0; {deselect the control}
- kCntlOn = 1; {control’s value when truned on}
- kCntlOff = 0; {control’s value when truned off}
-
- kButtonFrameSize = 3; {button’s frame pen size}
- kButtonFrameInset = -4; {inset rectangle adjustment around button}
- kButtonSizeH = 17; {standard height of buttons}
- kDafaultButSizeH = kButtonSizeH - kButtonFrameInset - kButtonFrameInset;
-
-
- {refer to the SoundApp.r file for the explaination about these numbers}
- kNumOfButtons = 6; {the number of buttons in the window}
- kSndStdSpacing = 8; {adjusts relative spacing of the entire window}
- kSndButtonSizeW = 100; {SizeW of buttons in document window}
- kSndButtonSizeH = 24; {heigth of buttons in document window}
- kSoundWindowSizeH = ((kNumOfButtons * kSndStdSpacing)
- + (kNumOfButtons * kSndButtonSizeH) + kSndStdSpacing);
- kSoundWindowSizeW = 296;
-
- kIconTop = 10; {standard position for icons in alerts}
- kIconLeft = 20;
- kIconBot = 42;
- kIconRight = 52;
- kIconPictGap = 8; {pict spacing in about window}
-
- kFSAsynch = TRUE; {asynchronous File Manager call}
-
- kEnterKey = CHR($03); {the keys I’m looking for}
- kReturnKey = CHR($0D);
- kEscape = CHR($1B);
- kUpArrow = CHR($1E);
- kDownArrow = CHR($1F);
- kPeriod = CHR($2E);
- kBackspace = CHR($08);
- kDelete = CHR($7F);
-
- {This bit set in the ioFlAttrib field if the file’s resource fork is open.}
- kResForkOpenBit = 2;
-
- {For the delay time when flashing the menubar and highlighting a button.}
- kDelayTime = 8; {8/60ths of a second}
-
- {The lowest volume I consider before suggesting the user increase it.}
- kMinVolumeDesired = 4;
-
- {The minimal number of ticks the ShowWindow needs to be visible.}
- kShowTimeDelay = 20;
-
- {BUG NOTE: Using a timbre value of 255 on the Mac Plus/SE will cause a crash.}
- kSquareWave = 240; {square wave for squareWaveSynth}
- kSineWave = 0; {sine wave for the squareWaveSynth}
- kPreferredTimbre = 190; {my preferred timbre for the squareWaveSynth}
-
- {Application snd resources must be higher than this number.}
- kSystemSndRange = 8191; {This is the highest snd id reserved by Apple.}
-
- {The following constants are the resource IDs.}
- rMenuBar = 1000; {application’s menu bar}
-
- rExitAlert = 1000; {emergency exit user alert}
- rUserAlert = 1001; {error message user alert}
- rSoundVolAlert = 1002; {sound is set low alert}
- rSaveAlert = 1003; {save changes? dialog}
-
- rGetNameDLOG = 1000; {get a name for the sound dialog}
- rNameItem = 3; {edit text item in rGetNameDLOG}
- rUserItem = 5; {user item to help draw default outline}
-
- rSFPGetFileDLOG = -4000; {dialog template for SFPGetFile}
- rSndOnlyCheckBox = 11; {dialog item number in SFPGetFile}
-
- {These are the window IDs used in the applications. Each one must be unique,
- since they are used to identify which window the event took place in.}
- rAboutWindow = 1000; {about window}
- rStatusWindow = 1001; {sound status window}
- rSoundWindow = 1002; {sound document window}
-
- rListRectID = 1000; {resource containing size of list rectangle}
-
- rCancelCntl = 1000; {stop button ID for the status window}
- rPlaySndCntl = 1001; {sound button IDs for the sound document window}
- rHyperPlayCntl = 1002;
- rPlayScaleCntl = 1003;
- rMelodyCntl = 1004;
- rStopCntl = 1005;
- rRecordCntl = 1006;
- rAboutOkCntl = 1007;
- rUntitled = 1000; {string ID for untitled resources}
- rAboutText = 1001; {string ID for text appearing in about window}
- rPutFileMsg = 1002; {string for text appearing in SFPutFile dialog}
- rMoofIcon = 1000; {resource ID for ICON of application}
- rAppPict = 1000; {resource ID of picture shown in about window}
- rSndCursor = 1000; {cursor resource for our documents}
-
- {The following are the snd resource IDs contained in the application,
- which start at ID 9000. IDs 0 - 8191 are reserved for Apple.}
- rMoofSound = 9000; {snd for the about window}
- rScaleSnd = 9001; {snd containing a scale}
- rMelodyPart1 = 9002; {snd containing a melody}
- rMelodyPart2 = 9003; {snd containing the harmony}
- rMelodyPart3 = 9004; {snd containing the harmony}
- rMelodyPart4 = 9005; {snd containing the harmony}
- rWaveHarmony = 9006; {snd containing waveTable for harmony}
- rWaveMelody = 9007; {snd containing waveTable for melody}
- rCounterPt1 = 9008; {snd containing soprano part of counter point}
- rCounterPt2 = 9009; {snd containing alto part of counter point}
- rCounterPt3 = 9010; {snd containing tenor part of counter point}
- rCounterPt4 = 9011; {snd containing bass part of counter point}
- rSopranoVox = 9012; {snd containing waveTable of soprano voice}
- rAltoVox = 9013; {snd containing waveTable of alto voice}
- rTenorVox = 9014; {snd containing waveTable of tenor voice}
- rBassVox = 9015; {snd containing waveTable of bass voice}
-
- {The following are resource IDs for messages.}
- sErrStrings = 1000; {error string STR# ID}
- sStandardErr = 1; {An error has occurred.}
- sMemErr = 2; {A Memory Manager error has occurred.}
- sResErr = 3; {A Resource Manager error has occurred.}
- sCurInUseErr = 4; {That file is currently in use.}
- sWavesBroken = 5; {The wave table synthesizer is not available.}
- sWrongVersion = 6; {This system does not support the Sound Manager...}
- sLowMemory = 7; {Memory is too low to continue...}
- sNoMenus = 8; {Could not find application’s menu resources.}
- sInitSoundErr = 9; {Could not initialize the Sound unit.}
- sSoundErr = 10; {The Sound Manager has encountered an error.}
- sNewDocErr = 11; {Could not create a new document.}
- sInitStatusErr = 12; {Error initializing the status window.}
- sEditErr = 13; {Could not complete the edit command.}
- sDocErr = 14; {There is a problem with this document.}
-
- sMsgStrings = 1001; {message string STR# ID}
- sPlayingMsg = 1; {playing a sound}
- sHyperMsg = 2; {playing a sound the Hyper way}
- sScaleMsg = 3; {playing scale}
- sMelodyMsg = 4; {playing melody}
- sTimbresMsg = 5; {playing various timbres}
- sCounterPtMsg = 6; {playing 4 part counter point}
-
- sSMErrStrings = 1002; {strings describing Sound Manager errors}
-
- {The following constants are used to identify menus and items. The menu IDs
- have an “m” prefix and the item numbers within each menu have an “i” prefix.}
- mApple = 128; {Apple menu and items}
- iAbout = 1;
-
- mFile = 129; {File menu and items}
- iNew = 1;
- iOpen = 2;
- iClose = 4;
- iQuit = 12;
-
- mEdit = 130; {Edit menu and items}
- iUndo = 1;
- iCut = 3;
- iCopy = 4;
- iPaste = 5;
- iClear = 6;
-
- mDemos = 1000; {Demos menu and items}
- iSquareScale = 1;
- iSquareMelody = 2;
- iSquareTimbre = 3;
- iWaveScale = 5;
- iWaveMelody = 6;
- iWaveSATB = 7;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- TYPE
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- BooleanPtr = ^BOOLEAN; {Ptr to a BOOLEAN, for type coercion}
- { IntegerPtr = ^INTEGER; Ptr to a INTEGER, for type coercion}
- { LongIntPtr = ^LongInt; Ptr to a LONGINT, for type coercion}
- { RectPtr = ^Rect; Ptr to a Rect resource for coercion}
- RectHandle = ^RectPtr; {Handle to a Rect resource for coercion}
-
- {This is a document layout that contains the window record and references
- to the associating data. The window record MUST be the first field. This
- is because I use the window pointer returned by the Toolbox to be a
- pointer to my document. To confirm that the window pointer is a document
- pointer, I store an application reference in the window record’s refCon.
- Then, I use a routine to test for the presence of this reference to insure
- I’m looking at one of my document’s windows.}
-
- SndDocument = RECORD
- window: WindowRecord; {must be first field}
- resFile: INTEGER; {document’s resource file}
- vRefNum: INTEGER; {real volume that contains the doc}
- dirID: LONGINT; {the dirID that contains the doc}
- list: ListHandle; {document’s list of sounds}
- sndInUse: BOOLEAN; {document is using a 'snd ' resource}
- END;
- SndDocPeek = ^SndDocument; {to peek at the document record}
-
- {This is the status window layout. The concept here is similar to the
- document type mentioned above. The message is a string handle used to
- store the current message.}
-
- StatusWindow = RECORD
- window: WindowRecord;
- message: StringHandle; {current text of status message}
- showTime: LONGINT; {time window was shown}
- END;
- StatWindowPeek = ^StatusWindow;
-
- {This is the about window layout. The concept here is similar to the
- document type mentioned above. The comment is a string handle used to
- store the current message.}
-
- AboutWindow = RECORD
- window: WindowRecord;
- comment: Handle; {handle to string of about comments}
- appIcon: Handle; {handle to icon or color icon}
- iconIsColor:Boolean; {TRUE if above icon is color}
- appPict: Handle; {handle to picture of app’s name}
- END;
- AboutWPeek = ^AboutWindow;
-
- {This is the template to the WIND resource. I used it to load in the WIND
- resource and then adjust the boundsRect. I also look at the procID to
- determine if it has a title bar or drag region.}
-
- WindowTemplate = RECORD {template to a WIND resource}
- boundsRect: Rect;
- procID: INTEGER;
- visible: BOOLEAN;
- filler1: BOOLEAN;
- goAwayFlag: BOOLEAN;
- filler2: BOOLEAN;
- refCon: LongInt;
- title: Str255;
- END;
- WindowTPtr = ^WindowTemplate;
- WindowTHndl = ^WindowTPtr;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- VAR {The “g” prefix is used to emphasize that a variable is global.}
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-
- {gMac is used to hold the result of a SysEnvirons call. This makes it
- convenient for any routine to check the environment. It is considered
- global information, anyway.}
-
- gMac: SysEnvRec; {set up by Initialize}
-
- {gReserveMemory is a handle of reserve memory used by my grow zone
- procedure. If memory is attempted to be allocated and fails, my grow
- zone will release this reserved block of memory.}
-
- gReserveMemory: Handle;
-
- {gStatusWindow is the window that tells the user what’s happening. I show
- it while a sound is playing, and remove it as soon as the sound is
- complete. It is initialized once during startup, and from then on two
- routines are used to show and hide it. This demonstrates orchestrating
- multimedia, that sound/graphic idea, at least in a crude sort of way.}
-
- gStatusWindow: StatWindowPeek;
-
- {gAppResRef is the application’s resource file reference. I need to save
- this since I can open other resource files. The current resource file is
- always gAppResRef unless I momentarily set it to another file to read its
- resources, and then immediately restore it back.}
-
- gAppResRef: INTEGER; {set up by Initialize}
-
- {gInBackground is maintained by our osEvent handling routines. Any part of
- the program can check it to find out if it is currently in the background.}
-
- gInBackground: BOOLEAN; {maintained by Initialize and DoEvent}
-
- {gSndFilesOnly is used to determine if the check box in the custom
- SFPGetFile dialog has been chosen. If this is true, then I want to only
- show files that contain 'snd ' resources.}
-
- gSndFilesOnly: BOOLEAN; {maintained by SFGetHook}
-
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- { IMPLEMENTATION }
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION MyGrowZone(cbNeeded: Size): LONGINT;
-
- {This is a very basic grow zone procedure. My application keeps a reserve
- handle of memory in case the Memory Manager gets a request for some memory
- that is not available in my heap. If memory were to get tight (<32k),
- the Toolbox will crash the system, especially Quickdraw. Before releasing
- the reserve handle I make sure it isn’t the GZSaveHnd. This handle cannot
- be touched by the grow zone procedure.
-
- WARNING: The grow zone procedure will be called and A5 may not be valid.
- Read Tech Note #136 and 208}
-
- VAR
- theA5: LONGINT;
-
- BEGIN
- theA5:= SetCurrentA5;
- IF (gReserveMemory^ <> NIL) & (gReserveMemory <> GZSaveHnd) THEN BEGIN
- EmptyHandle(gReserveMemory);
- MyGrowZone:= kSizeOfReserve; {released this much memory}
- END ELSE
- MyGrowZone:= 0; {this may release more memory}
- theA5:= SetA5(theA5);
- END; {MyGrowZone}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION LowOnReserve: BOOLEAN;
-
- VAR
- total, contig: LONGINT;
-
- {Before my application attempts to use more memory, I call this routine
- to check if I’m already using my reserve memory. If so, then I better
- prepare to die or get my reserve back.}
-
- BEGIN
- LowOnReserve:= gReserveMemory^ = NIL; {empty handle is low reserve}
- END; {LowOnReserve}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE RecoverReserve;
-
- {This is called from the event loop if LowOnReserve returns that I’m out of
- the reserve memory. This will recover the reserve memory block. If this
- fails, it will be called the next time through the event loop.}
-
- BEGIN
- ReallocateHandle(gReserveMemory, kSizeOfReserve);
- END; {RecoverReserve}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Initialization}
- FUNCTION AllocateReserve: BOOLEAN;
-
- {This is called at startup time to allocate the reserve memory block used
- in the grow zone procedure. If I’m unable to obtain this reserve, then
- return FALSE to signal a failure.}
-
- BEGIN
- gReserveMemory:= NewHandle(kSizeOfReserve);
- AllocateReserve:= gReserveMemory <> NIL;
- END; {AllocateReserve}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION FailLowMemory(memRequest: LONGINT): BOOLEAN;
-
- {Call PurgeSpace and see if the requested amount of memory exists in the
- heap including a minimal amount of heap space. Also, if my grow zone’s
- reserve has been release, that’s considered a failure. I don’t perform
- any purging here. The Memory Manager will do this if it needs the space.
- This routine can be called with a memRequest = 0. This checks if the heap
- space is getting critical.}
-
- VAR
- total, contig: LONGINT;
-
- BEGIN
- PurgeSpace(total, contig);
- FailLowMemory:= (total < (memRequest + kMinSpace)) | LowOnReserve;
- END; {FailLowMemory}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION HasSelection(sndDoc: SndDocPeek): BOOLEAN;
-
- {This a simple test to see if the user has a currently selected list item.}
-
- VAR
- aCell: Cell;
-
- BEGIN
- SetPt(aCell, 0, 0);
- HasSelection:= LGetSelect(TRUE, aCell, sndDoc^.list)
- END; {HasSelection}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION GetSelection(sndDoc: SndDocPeek; VAR sndHandle: Handle): OSErr;
-
- {Given a document, this will find the currently selected cell in the list.
- Then using this cell as an index number, I call Get1IndResource. This
- will return the proper handle. Since the List Manager is zero based and
- the Resource Manager isn’t, I have to add one to the index.
-
- BUG NOTE: GetIndResource will return a bogus handle if the index is not
- positive.}
-
- VAR
- aCell: Cell;
-
- BEGIN
- GetSelection:= noErr;
- SetPt(aCell, 0, 0);
- IF LGetSelect(TRUE, aCell, sndDoc^.list) THEN BEGIN
- IF aCell.v > -1 THEN BEGIN {GetIndResource doesn’t like < 0}
- UseResFile(sndDoc^.resFile); {only get our resources}
- sndHandle:= Get1IndResource('snd ', aCell.v + 1);
- IF sndHandle = NIL THEN
- GetSelection:= ResError; {return any error}
- UseResFile(gAppResRef); {restore our resource file}
- END;
- END;
- END; {GetSelection}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE SelectNextCell(list: ListHandle; next: BOOLEAN);
-
- {This is used to allow the user to select items from the list by using
- the arrow keys. Given a list and the direction, this will select the
- next cell. It will do nothing if we’re at the first cell and the user
- wants to do even higher (sped), or if we’re at the last cell and the
- user wants to go even lower (rent a clue bud).}
-
- VAR
- aCell: Cell;
- lastItem: INTEGER;
-
- PROCEDURE DoNextSelection;
- BEGIN
- LSetSelect(FALSE, aCell, list);
- IF next THEN
- aCell.v:= aCell.v + 1
- ELSE
- aCell.v:= aCell.v - 1;
- SetPt(aCell, aCell.h, aCell.v);
- LSetSelect(TRUE, aCell, list);
- LAutoScroll(list);
- END;
-
- BEGIN
- lastItem:= list^^.dataBounds.bottom - 1; {bounds is 1 greater}
- SetPt(aCell, 0, 0);
- IF LGetSelect(TRUE, aCell, list) THEN BEGIN
- IF (next & (aCell.v < lastItem)) | ((NOT next) & (aCell.v > 0)) THEN
- DoNextSelection;
- END ELSE {if no cells selected...}
- LSetSelect(TRUE, aCell, list); {select the first cell}
- END; {SelectNextCell}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE SelectSndCell(sndDoc: SndDocPeek; resID: INTEGER);
-
- {VERSION 1.1: This routine will go through the list of snd resources
- looking for the one that has the resource ID matching the parameter passed
- in. First thing to do is deselect the current selection. Then once the
- matching resource is found, select that one and scroll it into view. One
- assumption made is that the resources and the list are both index in the
- same order. This is true for the sndDoc, since I built the list this way.}
-
- VAR
- name: Str255;
- aCell: Cell;
- sndHndle: Handle;
- rType: ResType;
- theErr: OSErr;
- testID: INTEGER;
- index: INTEGER;
- numSnd: INTEGER;
- ignore: BOOLEAN;
-
- BEGIN
- index:= 0;
- SetPt(aCell, 0, 0);
- IF LGetSelect(TRUE, aCell, sndDoc^.list) THEN
- LSetSelect(FALSE, aCell, sndDoc^.list); {deselect any cell}
- UseResFile(sndDoc^.resFile); {count only its resources}
- numSnd:= Count1Resources('snd '); {number of sounds available}
- REPEAT
- index:= index + 1;
- SetResLoad(FALSE); {don’t load any resources}
- sndHndle:= Get1IndResource('snd ', index); {only get snd from file}
- SetResLoad(TRUE); {back to normal resource operations}
- GetResInfo(sndHndle, testID, rType, name);
- UNTIL ((resID = testID) | (index > numSnd));
- UseResFile(gAppResRef); {restore our resource file}
- SetPt(aCell, 0, index - 1);
- LSetSelect(TRUE, aCell, sndDoc^.list);
- LAutoScroll(sndDoc^.list);
- END; {SelectSndCell}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- FUNCTION InitSndList(docPtr: SndDocPeek): OSErr;
-
- {This is used to create the list showing all the snd resources in the file.
- If anything goes wrong while attempting to get any of the resources, then I
- return false. I don’t want the document to continue if something goes
- wrong while reading a resource. If I do get the resource, then I add its
- name to the list in a new row. I’m assured that at least one resource is
- in the file before this routine is called. Untitled resources will get one.}
-
- VAR
- index, resID,
- newRow, numSnd: INTEGER;
- resHandle: Handle;
- strHandle: StringHandle;
- itsType: ResType;
- resName, untitled:Str255;
- aCell: Cell;
-
- BEGIN
- InitSndList:= noErr;
- strHandle:= StringHandle(Get1Resource('STR ', rUntitled));
- IF strHandle <> NIL THEN
- untitled:= strHandle^^ {save no name title}
- ELSE
- untitled:= ''; {at least an empty string}
- UseResFile(docPtr^.resFile);
- numSnd:= Count1Resources('snd ');
- newRow:= LAddRow(numSnd, 0, docPtr^.list);
- FOR index:= 1 TO numSnd DO BEGIN
- SetResLoad(FALSE); {don’t load any resources}
- resHandle:= Get1IndResource('snd ', index); {only get snd from file}
- SetResLoad(TRUE); {back to normal resource operations}
- IF resHandle <> NIL THEN BEGIN {only if I got the snd}
- GetResInfo(resHandle, resID, itsType, resName);
- IF resName = '' THEN {if the snd isn’t named...}
- resName:= untitled; {give it a name}
- SetPt(aCell, 0, index - 1);
- LSetCell(Ptr(ORD(@resName) + 1), LENGTH(resName), aCell, docPtr^.list);
- END ELSE BEGIN {resHandle = NIL}
- InitSndList:= resNotFound; {problem with resource file}
- index:= numSnd + 1; {get out of the loop}
- END;
- END;
- UseResFile(gAppResRef); {restore our resource file}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- FUNCTION OpenByApp(vRefNum: INTEGER; dirID: LONGINT;
- fName: StringPtr; VAR window: WindowPtr): BOOLEAN;
-
- {In the case that the user has attempted to open a resource file that is
- already open, this routine will scan my open documents for it. If I find
- that the requested file is already open by the application, then return
- true and its window pointer. The application can then simply select that
- window. If the resource file is open but not by this application, that’s
- a problem and I don’t use the resource file. Many problems with using an
- open resource file. Now if the Resource Manager would take care of this
- problem for us...}
-
- VAR
- docName: Str255;
- testWindow: WindowPtr;
-
- BEGIN
- OpenByApp:= FALSE;
- testWindow:= FrontWindow;
- WHILE testWindow <> NIL DO BEGIN
- IF GetWRefCon(testWindow) = rSoundWindow THEN
- IF (vRefNum = SndDocPeek(testWindow)^.vRefNum)
- & (dirID = SndDocPeek(testWindow)^.dirID) THEN BEGIN
- GetWTitle(testWindow, docName);
- IF fName^ = docName THEN BEGIN
- window:= testWindow;
- OpenByApp:= TRUE;
- EXIT(OpenByApp);
- END;
- END;
- testWindow:= WindowPtr(WindowPeek(testWindow)^.nextWindow);
- END;
- END; {OpenByApp}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION GetGlobalMouse: Point;
-
- {Get the global coordinates of the mouse. Get the global coordinates by
- calling GetMouse and LocalToGlobal. This assumes the current port is a
- valid graf port. When wouldn’t it be?}
-
- VAR
- globalPt: Point;
-
- BEGIN
- GetMouse(globalPt);
- LocalToGlobal(globalPt);
- GetGlobalMouse:= globalPt;
- END; {GetGlobalMouse}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- FUNCTION GetWTitleHeight(variant: INTEGER): INTEGER;
-
- {Try and determine the window’s title bar height. This isn’t easy,
- especially if the window is invisible. We all know how the standard Apple
- System WDEF works, at least at this date we do. I assume that method as
- shown below. I could do what MacApp does with the Function CallWDefProc.
- If the user has installed a replacement for the System WDEF, then it’s
- their problem to deal with. My method will work as long as Apple doesn’t
- change how the WDEF in System 6.0 calculates the title bar height. This
- will allow my application to work on international Macs that have a larger
- system font than Chicago. I check the window’s variant code for one that
- includes a title bar. I will have to change this routine to adjust for
- the modal-moveable window type, which hasn’t been defined yet.
-
- In this routine, I violate my rule about not using the GetPort, SetPort,
- SetPort sequence mentioned at the start of the file. Mostly, I do this
- because it’s not all that apparent that a routine called GetWTitleHeight
- will change the port, so I make sure that it doesn’t.}
-
- VAR
- info: FontInfo;
- curGraf,
- wMgrPort: GrafPtr;
- wTitleHeight: INTEGER;
-
- BEGIN
- IF variant IN [documentProc, noGrowDocProc,
- zoomDocProc, zoomNoGrow, rDocProc] THEN BEGIN
- GetPort(curGraf);
- GetWMgrPort(wMgrPort); {I need to know the font...}
- SetPort(wMgrPort); {info in the System’s port}
- GetFontInfo(info);
- SetPort(curGraf); {restore current port}
- WITH info DO
- wTitleHeight:= ascent + descent + leading + 2;
- IF wTitleHeight < 19 THEN
- wTitleHeight:= 19; {the title is always at least 19}
- GetWTitleHeight:= wTitleHeight;
- END ELSE
- GetWTitleHeight:= 0; {other window types have no title}
- END; {GetWTitleHeight}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION GetGlobalTopLeft(window: WindowPtr): Point;
-
- {Given a window, this will return the top left point of the window’s
- port in global coordinates. Something this doesn’t include, is the
- window’s drag region (or title bar). This returns the top left point
- of the window’s content area only.
-
- In this routine, I violate my rule about not using the GetPort, SetPort,
- SetPort sequence mentioned at the start of the file. Mostly, I do this
- because it’s not all that apparent that a routine called GetGlobalTopLeft
- will change the port, so I make sure that it doesn’t.}
-
- VAR
- theGraf: GrafPtr;
- globalPt: Point;
-
- BEGIN
- GetPort(theGraf);
- SetPort(window);
- globalPt:= window^.portRect.topLeft;
- LocalToGlobal(globalPt);
- SetPort(theGraf);
- GetGlobalTopLeft:= globalPt;
- END; {GetGlobalTopLeft}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE MyParamText(text: StringHandle; cite0, cite1: StringPtr);
-
- {I’m using string pointers (not necessarily Memory Manager pointers)
- to the replacement strings. Using Str255 would copy the string to the
- stack, and then to the text handle. This way it is only copied once.}
-
- VAR
- offSet: LONGINT;
- param0,
- param1: PACKED ARRAY [1..2] OF CHAR;
- newLength: LONGINT;
-
- BEGIN
- param0:= '^0';
- param1:= '^1';
- IF cite0^ <> '' THEN
- offSet:= Munger(Handle(text), 1, @param0, SIZEOF(param0),
- Ptr(ORD4(cite0) + 1), LENGTH(cite0^));
- IF cite1^ <> '' THEN
- offSet:= Munger(Handle(text), 1, @param1, SIZEOF(param1),
- Ptr(ORD4(cite1) + 1), LENGTH(cite1^));
- newLength:= GetHandleSize(Handle(text)) - 1;
- IF newLength > 255 THEN
- newLength:= 255;
- text^^[0]:= CHAR(newLength); {string’s new length byte}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- PROCEDURE CenterWindowRect(variant: INTEGER; VAR theRect: Rect);
-
- {Given a window’s portRect, this routine will center the rectangle on the
- main device within the desktop region. This excludes the menu bar area.
- It follows the Apple Human Interface Guidelines for where to place a
- window centered on the screen. That is, 1/3th of the desktop shows above
- the window and 2/3ths below it. It returns a new rectangle set to the
- centered position. The top and left of this rectangle can be used with
- MoveWindow. This routine only considers the main device. CenterWindowRect
- could take a GDevice as a parameter to center the VAR rect with. This
- would also allow Alerts or dialogs that are closely associated with a
- document window to be centered relative to the monitor that contains that
- document. This is also one of the Human Interface Guidelines. MacApp 2.0
- has a utility CalcScreenRect that you can borrow from to include this.
-
- WARNING: This routine may move or purge memory.}
-
- VAR
- rectSize: Point;
- wTitleHeight: INTEGER;
-
- BEGIN
- wTitleHeight:= GetWTitleHeight(variant); {get title height}
- WITH theRect DO BEGIN {get size of rect}
- SetPt(rectSize, right, bottom + wTitleHeight); {include it in size}
- SubPt(topLeft, rectSize);
- END;
- WITH qd.screenBits.bounds DO BEGIN {1/3th below menubar}
- theRect.top:= ((bottom - top - GetMBarHeight - rectSize.v) DIV 3)
- + GetMBarHeight;
- theRect.left:= ((right - left) - rectSize.h) DIV 2; {centered horz}
- END;
- WITH theRect DO BEGIN {return adjusted rect}
- SetPt(botRight, left, top);
- AddPt(rectSize, botRight);
- top:= top + wTitleHeight; {remove title height from rect}
- END;
- END; {CenterWindowRect}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- FUNCTION GetCenteredWindow(id: INTEGER; p: Ptr; behind: WindowPtr): WindowPtr;
-
- {Given a window ID, this routine will center the window’s rectangle before
- showing it on the main screen. This follows the Apple Human Interface
- Guidelines for where to place a centered window on the screen. If the
- window is closely associated with another window, considerations should be
- given to where that other window is located. If this other window is on a
- screen other then the main monitor, then it would be best to center the
- window on that other monitor.
-
- VERSION 1.1: There is a problem with GetNewWindow in the old Mac Plus and
- SE ROMS. It will call ReleaseResource on the 'WIND' resource within
- GetNewWindow. This make the handle invalid after the call. Therefore,
- before calling HPurge, we get the resource once again.}
-
- VAR
- newRect: Rect;
- windTemplate: WindowTHndl;
- window: WindowPtr;
-
- BEGIN
- window:= NIL; {initialize result}
- windTemplate:= WindowTHndl(Get1Resource('WIND', id));
- IF windTemplate <> NIL THEN BEGIN
- HNoPurge(Handle(windTemplate));
- newRect:= windTemplate^^.boundsRect;
- CenterWindowRect(windTemplate^^.procID, newRect);
- windTemplate^^.boundsRect:= newRect;
- window:= GetNewWindow(id, p, behind);
- HPurge(Get1Resource('WIND', id));
- END;
- GetCenteredWindow:= window;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- FUNCTION CenteredAlert(alertID: INTEGER): INTEGER;
-
- {Given an Alert ID, this routine will center the alert’s rectangle before
- showing it on the main screen. This follows the Apple Human Interface
- Guidelines for where to place a centered window on the screen. If the
- Alert is closely associated with another window, considerations should be
- given to what the window is located. If this other window is on a screen
- other then the main monitor, then it would be best to center the Alert on
- that other monitor.}
-
- VAR
- alertHandle: AlertTHndl;
- alertRect: Rect;
- itemHit: INTEGER;
-
- BEGIN
- alertHandle:= AlertTHndl(Get1Resource('ALRT', alertID));
- IF alertHandle <> NIL THEN BEGIN
- HNoPurge(Handle(alertHandle));
- alertRect:= alertHandle^^.boundsRect;
- CenterWindowRect(dBoxProc, alertRect);
- alertHandle^^.boundsRect:= alertRect;
- HPurge(Handle(alertHandle));
- END;
- CenteredAlert:= Alert(alertID, NIL);
- END; {CenteredAlert}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- FUNCTION GetCenteredDialog(id: INTEGER; p: Ptr; behind: WindowPtr): DialogPtr;
-
- {Given a dialog ID, this routine will center the dialog’s rectangle before
- showing it on the main screen. This follows the Apple Human Interface
- Guidelines for where to place a centered window on the screen. If the
- dialog is closely associated with another window, considerations should be
- given to what the window is located. If this other window is on a screen
- other then the main monitor, then it would be best to center the dialog on
- that other monitor.
-
- VERSION 1.1: There is a problem with GetNewDialog in the old Mac Plus and
- SE ROMS. It will call ReleaseResource on the 'WIND' resource within
- GetNewWindow. This make the handle invalid after the call. Therefore,
- before calling HPurge, we get the resource once again.}
-
- VAR
- newRect: Rect;
- dlogTemplate: DialogTHndl;
- dialog: DialogPtr;
-
- BEGIN
- dialog:= NIL; {initialize result}
- dlogTemplate:= DialogTHndl(Get1Resource('DLOG', id));
- IF dlogTemplate <> NIL THEN BEGIN
- newRect:= dlogTemplate^^.boundsRect;
- CenterWindowRect(dlogTemplate^^.procID, newRect);
- dlogTemplate^^.boundsRect:= newRect;
- HNoPurge(Handle(dlogTemplate));
- dialog:= GetNewDialog(id, p, behind);
- HPurge(Get1Resource('DLOG', id));
- END;
- GetCenteredDialog:= dialog;
- END; {GetCenteredDialog}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoButtonOutline(button: ControlHandle);
-
- {Given any control handle, this will draw an outline around it. This is
- used for the default button of a window. The extra nice feature here is
- that I’ll erase the outline for buttons that are inactive. Seems like
- there should be a Toolbox call for getting a control’s hilite state.
- Since there isn’t, I have to look into the control record myself. This
- should be called for update and activate events.
-
- The method for determining the oval diameters for the roundrect is a
- little different than that recommended by Inside Mac. IM I-407 suggests
- that you use a hardcoded (16,16) for the diameters. However, this only
- looks good for small roundrects. For larger ones, the outline doesn’t
- follow the inner roundrect because the CDEF for simply buttons doesn’t
- use (16,16). Instead, it uses half the height of the button as the
- diameter. By using this formula, too, our outlines look better.
-
- WARNING: This will set the current port to the control’s window.}
-
- VAR
- theRect: Rect;
- curPen: PenState;
- buttonOval: INTEGER;
-
- BEGIN
- IF button <> NIL THEN BEGIN
- SetPort(button^^.contrlOwner);
- GetPenState(curPen);
- PenNormal;
- theRect:= button^^.contrlRect;
- InsetRect(theRect, kButtonFrameInset, kButtonFrameInset);
- buttonOval := (theRect.bottom - theRect.top) DIV 2;
- IF (button^^.contrlHilite = kCntlActivate) THEN
- PenPat(qd.black)
- ELSE
- PenPat(qd.gray);
- PenSize(kButtonFrameSize, kButtonFrameSize);
- FrameRoundRect(theRect, buttonOval, buttonOval);
- SetPenState(curPen);
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE SelectButton(button: ControlHandle);
-
- {Given the button control handle, this will cause the button to look as
- if it has been clicked in. This is nice to do for the user if they type
- return or enter to select the default item.}
-
- VAR
- finalTicks: LONGINT;
-
- BEGIN
- HiliteControl(button, kSelect);
- Delay(kDelayTime, finalTicks);
- HiliteControl(button, kDeselect);
- END; {SelectButton}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE ActivateSndCntls(sndDoc: SndDocPeek);
-
- {Given a sound document, this routine goes through the control list looking
- for a control that needs to be activated, or deactivated what ever the
- case maybe. The Play button is the default button, and it also needs its
- outline drawn. The Stop button is always active on the front most window.
- This is true even if there is no selection made in the window’s list. The
- Stop button is a global action, regardless of what is happening in the window.
-
- VERSION 1.1: Stop button is only active while a sound is playing.}
-
- VAR
- control: ControlHandle;
- cntlRefCon: LONGINT;
- activate: BOOLEAN;
-
- BEGIN
- activate:= WindowPeek(sndDoc)^.hilited & HasSelection(sndDoc);
- control:= WindowPeek(sndDoc)^.controlList;
- WHILE control <> NIL DO BEGIN
- cntlRefCon:= GetControlReference(control);
- IF cntlRefCon IN [rPlaySndCntl, rHyperPlayCntl,
- rPlayScaleCntl, rMelodyCntl] THEN BEGIN
- IF activate THEN
- HiliteControl(control, kCntlActivate)
- ELSE
- HiliteControl(control, kCntlDeactivate);
- IF cntlRefCon = rPlaySndCntl THEN
- DoButtonOutline(control);
- END; {IF cntlRefCon IN}
- IF cntlRefCon = rStopCntl THEN BEGIN
- IF WindowPeek(sndDoc)^.hilited & SndChanOpen THEN
- HiliteControl(control, kCntlActivate)
- ELSE
- HiliteControl(control, kCntlDeactivate);
- END; {cntlRefCon = rStopCntl}
- IF cntlRefCon = rRecordCntl THEN BEGIN
- IF WindowPeek(sndDoc)^.hilited THEN
- HiliteControl(control, kCntlActivate)
- ELSE
- HiliteControl(control, kCntlDeactivate);
- END; {cntlRefCon = rRecordCntl}
- control:= control^^.nextControl;
- END; {WHILE control <> NIL}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION IsDAWindow(window: WindowPtr): BOOLEAN;
-
- {Check if a window belongs to a desk accessory. This will first test for a
- NIL window. DAs will have a negitive windowKind.}
-
- BEGIN
- IF window = NIL THEN
- IsDAWindow:= FALSE
- ELSE {DA windows have negative windowKinds}
- IsDAWindow:= WindowPeek(window)^.windowKind < 0;
- END; {IsDAWindow}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION IsDocWindow(window: WindowPtr): BOOLEAN;
-
- {Check to see if a window is a document window. This will first test for a
- NIL window. Document windows all have a refCon set to rSoundWindow. This
- insures there is document type information after the window record.}
-
- BEGIN
- IF window = NIL THEN
- IsDocWindow:= FALSE
- ELSE
- IsDocWindow:= GetWRefCon(window) = rSoundWindow;
- END; {IsDocWindow}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION IsModalWindow(window: WindowPtr): BOOLEAN;
-
- {This test will return TRUE for a window that needs to be treated as a
- modal window. To include additional windows, add the window’s refCon
- value to the set of modal windows.}
-
- VAR
- wRef: LONGINT;
-
- BEGIN
- IF window = NIL THEN
- IsModalWindow:= FALSE
- ELSE BEGIN
- wRef:= GetWRefCon(window);
- IsModalWindow:= wRef IN [rAboutWindow]; {set of modal windows}
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE KillSound;
-
- {This is used to free up all the sound data and channels in use. There are
- two reasons for using this routine. One is after a sound has completed
- the other is to terminate a sound that may be in progress. For this
- second reason, this routine should always be used before playing a new
- sound. When disposing of a sound channel, this sets all document’s
- sndInUse flags to FALSE. I can call this routine at any time. When I
- find that the memory reserve is used, I will call this to free up any
- possible sound resources which tend to be large. Another important point
- is the human interface issue of hiding the status window. It’s possible
- that playing some short sounds could cause the status window to disappear
- before the user had much of a chance to see it. To solve this, there is a
- delay that insures the status window was visible for a minimal time.
-
- VERSION 1.1: Update the document's controls since know that the sound
- is no longer playing the buttons in the document window need to be updated.}
-
- VAR
- window: WindowPtr;
-
- BEGIN
- IF SndChanOpen THEN BEGIN {if a channel is open...}
- window:= FrontWindow; {set all document’s flags}
- WHILE window <> NIL DO BEGIN
- IF IsDocWindow(window) THEN {if this is my document...}
- SndDocPeek(window)^.sndInUse:= FALSE; {then it is no longer active}
- window:= WindowPtr(WindowPeek(window)^.nextWindow);
- END;
- END;
- IF SoundCompletion THEN BEGIN {why were we called?}
- DoSoundComplete; {from completion}
- REPEAT {delay for status window}
- UNTIL ((TickCount - gStatusWindow^.showTime) > kShowTimeDelay);
- END ELSE
- FreeAllChans; {or just because}
- HideWindow(WindowPtr(gStatusWindow));
- window:= FrontWindow;
- IF IsDocWindow(window) THEN
- ActivateSndCntls(SndDocPeek(window));
- END; {KillSound}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION DoCloseWindow(window: WindowPtr): BOOLEAN;
-
- {Close any of my windows. At this point, if there was a document
- associated with a window, you could do any document saving processing if
- it has been changed since being opened. DoCloseWindow would return TRUE
- if the window actually closed. FALSE would be returned, as an example, if
- the user chose “Cancel” in the Save As… dialog. An important detail is if
- the window to be close is one of my documents that is currently playing a
- sound. We cannot dispose of it yet, since the sound resource owned by the
- document would be in use by the Sound Manager. So, I test the document’s
- sndInUse flag and if it is in use I call KillSound. The About window
- contains some resource handles. I only mark them purgeable, but I could
- use ReleaseResource and then DisposeHandle.}
-
- BEGIN
- DoCloseWindow:= TRUE;
- CASE GetWRefCon(window) OF
-
- rSoundWindow: BEGIN
- IF SndDocPeek(window)^.sndInUse THEN {contain a snd in use?}
- KillSound; {not any more}
- IF SndDocPeek(window)^.list <> NIL THEN {dispose of document data}
- LDispose(SndDocPeek(window)^.list);
- IF SndDocPeek(window)^.resFile <> 0 THEN
- CloseResFile(SndDocPeek(window)^.resFile);
- KillControls(window);
- CloseWindow(WindowPtr(window));
- DisposePtr(Ptr(window)); {dispose of our doc storage}
- END;
-
- rStatusWindow: BEGIN
- KillControls(WindowPtr(gStatusWindow));
- DisposeHandle(Handle(gStatusWindow^.message));
- CloseWindow(WindowPtr(gStatusWindow));
- DisposePtr(Ptr(gStatusWindow));
- END;
-
- rAboutWindow: BEGIN
- IF AboutWPeek(window)^.comment <> NIL THEN {never dispose...}
- HPurge(AboutWPeek(window)^.comment); {a Resource handle}
- IF AboutWPeek(window)^.appPict <> NIL THEN
- HPurge(AboutWPeek(window)^.appPict);
- IF AboutWPeek(window)^.appIcon <> NIL THEN
- IF AboutWPeek(window)^.iconIsColor THEN
- DisposeCIcon(CIconHandle(AboutWPeek(window)^.appIcon))
- ELSE
- HPurge(AboutWPeek(window)^.appIcon); {disposCIcon}
- KillControls(window);
- CloseWindow(WindowPtr(window));
- DisposePtr(Ptr(window));
- END;
-
- OTHERWISE
- IF IsDAWindow(window) THEN {we can close DAs too}
- CloseDeskAcc(WindowPeek(window)^.windowKind);
-
- END; {CASE GetWRefCon(window)}
- END; {DoCloseWindow}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE AlertUser(error: INTEGER; messageID: INTEGER);
-
- {Display an alert to inform the user of an error. MessageID acts as an
- index into a STR# resource of error messages. This will attempt to
- replace the error number with a string if the sound is a Sound Manager
- error.
-
- BUG NOTE: GetIndString will return a bogus string if the index is not
- positive.}
-
- VAR
- msg1, msg2: Str255;
- theItem: INTEGER;
-
- BEGIN
- UseResFile(gAppResRef); {restore our resource file}
- IF messageID > 0 THEN
- GetIndString(msg1, sErrStrings, messageID)
- ELSE
- msg1:= ''; {in case there is no message}
- IF (error <= noHardware) & (error >= siUnknownQuality) THEN
- GetIndString(msg2, sSMErrStrings, ABS(error) + noHardware + 1)
- ELSE
- NumToString(error, msg2);
- ParamText(msg1, msg2, '', '');
- theItem:= CenteredAlert(rUserAlert);
- END; {AlertUser}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE EmergencyExit(message: INTEGER);
-
- {Display an alert that tells the user an error occurred, then exit the
- program. This routine is used as an ultimate bail-out for serious errors
- that prohibit the continuation of the application. Errors that do not
- require the termination of the application are handled with AlertUser.
- Error checking and reporting has a place even in the simplest application.
-
- BUG NOTE: GetIndString will return a bogus string if the index is not
- positive.}
-
- VAR
- msg1: Str255;
- theItem: INTEGER;
-
- BEGIN
- SetCursor(qd.arrow);
- IF message > 0 THEN
- GetIndString(msg1, sErrStrings, message)
- ELSE
- msg1:= ''; {in case there is no message}
- ParamText(msg1, '', '', '');
- theItem:= CenteredAlert(rExitAlert);
- ExitToShell; {we’re out of here}
- END; {EmergencyExit}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE Terminate;
-
- {Clean up the application and exit. I close all of the windows so that
- they can update their documents, if any. Dispose of the Sound Unit and
- close the status window if the user really wants to quit.}
-
- VAR
- aWindow: WindowPtr;
- closed: BOOLEAN;
-
- BEGIN
- closed:= TRUE;
- KillSound; {stop any sound in progress}
- REPEAT
- aWindow:= FrontWindow; {get the current front window}
- IF aWindow <> NIL THEN
- closed:= DoCloseWindow(aWindow); {close this window}
- UNTIL (NOT closed) | (aWindow = NIL); {do all windows}
- IF closed THEN BEGIN
- FreeSoundUnit; {get rid of all sound equipment}
- ExitToShell; {exit if no cancellation}
- END;
- END; {Terminate}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DrawStatusWindow;
-
- {Draw the status message and update the button. I setup a rectangle that
- is the text area. This is the entire window area, inset a small amount.
- I also subtract from the bottom of the rectangle the area used by the button.
- Doing things this way allowed me to change the size of the window and
- not change any of this code. The rectangle used by text will fill the
- window regardless of the new size I give it in the WIND resource.}
-
- VAR
- theRect: Rect;
-
- BEGIN
- PenNormal;
- WITH WindowPtr(gStatusWindow)^.portRect DO
- SetRect(theRect, left, top, right, bottom - kDafaultButSizeH);
- InsetRect(theRect, kMsgInset, kMsgInset);
- HLock(Handle(gStatusWindow^.message));
- TETextBox(Ptr(ORD(gStatusWindow^.message^) + 1),
- LENGTH(gStatusWindow^.message^^), theRect, teJustCenter);
- HUnlock(Handle(gStatusWindow^.message));
- UpdateControls(WindowPtr(gStatusWindow), WindowPtr(gStatusWindow)^.visRgn);
- DoButtonOutline(WindowPeek(gStatusWindow)^.controlList);
- END; {DrawStatusWindow}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE ShowStatusWindow(messageID: INTEGER);
-
- {Whenever I start a sound, I show this status window. It will contain a
- message showing what is currently happening. A very good rule is not to
- perform drawing outside of the update event. I bend that rule here. I
- could simply select this window and show it. This would allow my normal
- drawing and activating routines to be called. The problem with that was,
- I found that many times the status window appeared and then disappeared
- before these update routines had a chance to draw the window. This left
- the user with a blank window that immediately went away. So, I do the
- activation and drawing right after showing it.
-
- BUG NOTE: GetIndString will return a bogus string if the index is not
- positive.}
-
- VAR
- msg: Str255;
-
- BEGIN
- IF FailLowMemory(0) THEN BEGIN {running low on memory?}
- KillSound;
- AlertUser(memFullErr, sLowMemory);
- END ELSE BEGIN
- IF messageID > 0 THEN
- GetIndString(msg, sMsgStrings, messageID)
- ELSE
- msg:= ''; {case there’s no message}
- SetString(gStatusWindow^.message, msg);
- ShowWindow(WindowPtr(gStatusWindow)); {show the window}
- SelectWindow(WindowPtr(gStatusWindow)); {bring it to the front}
- SetPort(GrafPtr(gStatusWindow));
- EraseRect(WindowPtr(gStatusWindow)^.portRect); {erase the old message}
- HiliteControl(WindowPeek(gStatusWindow)^.controlList, kCntlActivate);
- DrawStatusWindow; {draw the new message}
- ValidRect(WindowPtr(gStatusWindow)^.portRect); {avoid a needless update}
- gStatusWindow^.showTime:= TickCount; {it’s show time folks}
- END;
- END; {ShowStatusWindow}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Initialization}
- PROCEDURE InitStatusWindow;
-
- {This initializes the status window, and keeps it hidden until the user
- decides to play a sound. I also center it for the first time, but the
- user is free to drag it to a new location afterwards. The status window
- also has a message associated to it, so I allocate a string handle for this.}
-
- VAR
- window: WindowPtr;
- stopButton: ControlHandle;
-
- BEGIN
- gStatusWindow:= StatWindowPeek(NewPtrClear(SIZEOF(StatusWindow)));
- IF gStatusWindow = NIL THEN
- EmergencyExit(sInitStatusErr);
- window:= GetCenteredWindow(rStatusWindow, Ptr(gStatusWindow), Pointer(-1));
- IF window = NIL THEN
- EmergencyExit(sInitStatusErr);
- SetWRefCon(window, rStatusWindow);
- stopButton:= GetNewControl(rCancelCntl, window);
- IF stopButton = NIL THEN
- EmergencyExit(sInitStatusErr);
- gStatusWindow^.message:= NewString(''); {a new empty string handle}
- IF gStatusWindow^.message = NIL THEN
- EmergencyExit(sInitStatusErr);
- HNoPurge(Handle(gStatusWindow^.message));
- END; {InitStatusWindow}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoErrorSound(soundNo: INTEGER);
-
- {I just flash the menubar for a moment instead of playing any sounds. It’s
- the same effect as setting the sound volume to zero but that would prevent
- me playing my sounds. Also, it helps to prevent the problem of _SysBeep
- being called by the Dialog Manager while I’m playing a sound. If my
- application is playing a sound and, for example, the user clicks outside
- of the Standard File dialog ModalDialog calls _SysBeep. This can cause
- the Mac to crash or trash my channel.
-
- BUG NOTE: If the current Sound Manager were playing a sound and a
- _SysBeep were to occur, bad things could happen on a Mac Plus/SE. Either
- the application’s channel would be trashed or the Mac could crash.
-
- VERSION 1.1: The new Sound Manager will handle the problem presented in the
- older Sound Manager regarding SysBeep. If I'm running under the new Sound
- Manager then I'll call SysBeep anyway. The new Sound Manager will properly
- handle the situation of an open sound channel when SysBeep is called.}
-
- VAR
- finalTicks: LongInt;
-
- BEGIN
- IF HasNewSndMgr THEN
- SysBeep(30) {does the right thing now}
- ELSE BEGIN
- IF soundNo > 0 THEN BEGIN {only after the first time}
- FlashMenuBar(0);
- Delay(kDelayTime, finalTicks);
- FlashMenuBar(0);
- END;
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Initialization}
- PROCEDURE CheckSndVol;
-
- {This tests if the volume has been set very low. It’s a judgement call as
- to what “too low” means. The user sets the volume, so how could it be too
- low? A setting of 0 could be considered too low, but in any case do not
- adjust it. I find the volume goes below 4 on a standard Mac, then there
- is a perceivable difference. I also wanted to show this routine as a
- demonstration in how to handle such a situation. Do not change it
- directly! Ask the user to do it. Consider the user that has their Mac
- connected to a stack of Marshalls (which go to 11) and has purposely set
- their Mac’s volume where they wanted it. This may be 3 and if you crack
- it up to 8, it could be a scene out of Back to the Future.
-
- User Interface rule, let the user remain in control!}
-
- VAR
- curVol,
- item: INTEGER;
- numStr: Str255;
-
- BEGIN
- GetSoundVol(curVol);
- IF (curVol < kMinVolumeDesired) THEN BEGIN
- NumToString(curVol, numStr);
- ParamText(numStr, '', '', ''); {show the current volume}
- item:= CenteredAlert(rSoundVolAlert);
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- PROCEDURE AddSndDocControls(window: WindowPtr);
-
- {Simply add all the buttons I want to use in the document window. I get
- the control templates, and add the control to the window. I don’t check
- for getting errors for two reasons. I put these controls there. If the
- user removes them, it’s their problem. If the application cannot get the
- memory required for these, then the application is already out of memory
- and checking here probably wouldn’t help much. I do check before creating
- the document that there was enough available memory. I save the resource
- ID into the control’s refCon so later I can tell which control was hit by
- the user.
-
- VERSION 1.1: Add the record button to the window.}
-
- VAR
- control: ControlHandle;
-
- BEGIN
- control:= GetNewControl(rPlaySndCntl, window);
- IF control <> NIL THEN
- SetControlReference(control, rPlaySndCntl);
- control:= GetNewControl(rHyperPlayCntl, window);
- IF control <> NIL THEN
- SetControlReference(control, rHyperPlayCntl);
- control:= GetNewControl(rPlayScaleCntl, window);
- IF control <> NIL THEN
- SetControlReference(control, rPlayScaleCntl);
- control:= GetNewControl(rMelodyCntl, window);
- IF control <> NIL THEN
- SetControlReference(control, rMelodyCntl);
- control:= GetNewControl(rStopCntl, window);
- IF control <> NIL THEN
- SetControlReference(control, rStopCntl);
- control:= GetNewControl(rRecordCntl, window);
- IF control <> NIL THEN
- SetControlReference(control, rRecordCntl);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- FUNCTION NewStackedWindow(windID: INTEGER; windStorage: Ptr): WindowPtr;
-
- {There are a few approaches I thought of and tried, none of which satisfied
- me. ResEdit uses the simplest method, an offset from the front window. I
- don’t like this because it may cover up existing windows. So my approach is
- to go through the window list looking for an empty spot. If the empty
- spot causes the window to go off the screen, then I start a new stack off
- to the right. This will cause a new series of window to be stacked. If the
- new stack would cause the window to go off screen, then it’s time to give up.
- All new windows will then be moved to the starting point and there is no
- further stacking. If I look at this routine too much, I get real tired.
- If I try to change it at all, I get woozy trying to make it work again.}
-
- CONST
- kStartPt = 2; {offset from the topLeft of the screen}
- kStaggerH = 16; {staggering amounts for new windows}
- kStaggerV = 3; {not including the window’s title height}
-
- VAR
- windSize, newPt,
- oldPt, delta: Point;
- newWindow,
- oldWindow: WindowPtr;
- wTitleHeight,
- numStacks: INTEGER;
- taken: BOOLEAN;
-
- FUNCTION PositionAvailable: BOOLEAN;
- BEGIN
- taken:= FALSE;
- oldWindow:= FrontWindow;
- WHILE (oldWindow <> NIL) & NOT taken DO BEGIN
- IF IsDocWindow(oldWindow) & WindowPeek(oldWindow)^.visible THEN BEGIN
- oldPt:= GetGlobalTopLeft(oldWindow);
- delta.v:= ABS(newPt.v - oldPt.v);
- delta.h:= ABS(newPt.h - oldPt.h);
- taken:= (delta.h + delta.v)
- <= ((kStaggerH + kStaggerV + wTitleHeight) DIV 2);
- END;
- oldWindow:= WindowPtr(WindowPeek(oldWindow)^.nextWindow);
- END;
- PositionAvailable:= NOT taken;
- END;
-
- FUNCTION OutOfBounds: BOOLEAN;
- BEGIN
- OutOfBounds:= ((newPt.v + windSize.v) > qd.screenBits.bounds.bottom)
- | ((newPt.h + windSize.h) > qd.screenBits.bounds.right);
- END;
-
- BEGIN {set the initail starting point for a window, less the staggering amount}
- newWindow:= GetNewWindow(windID, windStorage, WindowPtr(-1));
- windSize:= newWindow^.portRect.botRight;
- SubPt(newWindow^.portRect.topLeft, windSize);
- wTitleHeight:= GetWTitleHeight(GetWVariant(newWindow));
- SetPt(newPt, kStartPt - kStaggerH, kStartPt - kStaggerV + GetMBarHeight);
-
- REPEAT {add the staggering amount then test if the window goes off the screen}
- taken:= TRUE;
- SetPt(newPt, newPt.h + kStaggerH, newPt.v + kStaggerV + wTitleHeight);
- IF OutOfBounds THEN BEGIN
- numStacks:= numStacks + 1;
- SetPt(newPt, kStartPt + (numStacks * kStaggerH),
- kStartPt + wTitleHeight + GetMBarHeight);
- IF OutOfBounds THEN BEGIN
- SetPt(newPt, kStartPt, kStartPt + wTitleHeight + GetMBarHeight);
- taken:= FALSE;
- END;
- END;
- UNTIL NOT taken | PositionAvailable;
-
- MoveWindow(newWindow, newPt.h, newPt.v, TRUE);
- NewStackedWindow:= newWindow;
- END; {NewStackedWindow}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- FUNCTION CreateSoundDoc(resRef: INTEGER; fNamePtr: StringPtr;
- vRefNum: INTEGER; dirID: LONGINT): OSErr;
-
- {Create a sound document and initialize all the data associated with
- it. If I encounter an error, then dispose of any memory allocated in the
- attempt. Allocate all necessary memory required for a sound document. I
- like to use NewPtrClear because is will initialize all the memory. I
- depend on real values or zero in my document fields. Then its time to
- build a sound document which contains all the controls and the list of
- sounds. I get the position for the list kept in a rectangle resource.
- This helps me to adjust the size of the list without changing code. I
- will make an additional adjustment to the list’s height to insure better
- scrolling. This avoids the ugly white space of an improperly sized
- rectangle. If I cannot create the list, I’ll close the window and return
- a NIL. If I find after creating a new document that I’m low on memory,
- I’ll close it and return an error.
-
- SPECIAL NOTE: The Human Interface Group suggested that the buttons and the
- list should in the System font. I designed these windows using the
- application font in 10 point. If you change the call to TextFont, you'll
- find that the buttons and the list will work properly in your font choice.
-
- VERSION 1.1: Size the window smaller to hide the record button from the
- user if Sound Input is not available. Added memory check to the beginning
- of this routine before creating the document. This was done because there
- are two routines that call this one, OpenSoundDoc and NewSoundDoc which
- both performed this check. Having here in one place reduces the chance of
- future bugs (doing the same thing in two places) and reduces the size of
- the code.}
-
- VAR
- newDocPtr: SndDocPeek;
- window: WindowPtr;
- list: ListHandle;
- rHandle: RectHandle;
- lView, lBounds: Rect;
- aCell: Cell;
- cSize, newSize: Point;
- maxListHeight: INTEGER;
- theErr: OSErr;
- ignore: BOOLEAN;
-
- BEGIN
- IF FailLowMemory(kMemForSndDoc) THEN BEGIN
- CloseResFile(resRef); {close it before leaving}
- CreateSoundDoc:= memFullErr;
- EXIT(CreateSoundDoc);
- END;
- newDocPtr:= SndDocPeek(NewPtrClear(SIZEOF(SndDocument)));
- theErr:= MemError;
- IF newDocPtr <> NIL THEN BEGIN
- window:= NewStackedWindow(rSoundWindow, Ptr(newDocPtr));
- SetPort(window);
- IF HasSoundInput THEN
- SizeWindow(window, kSoundWindowSizeW, kSoundWindowSizeH, FALSE);
- TextFont(0); {set window to System font, blech}
- AddSndDocControls(window); {add my buttons}
- SetWRefCon(WindowPtr(newDocPtr), rSoundWindow); {mark as an app window}
- SetWTitle(WindowPtr(newDocPtr), fNamePtr^);
- newDocPtr^.resFile:= resRef; {save its resource file ref}
- newDocPtr^.vRefNum:= vRefNum; {save its volume reference}
- newDocPtr^.dirID:= dirID; {save its directory ID}
- newDocPtr^.sndInUse:= FALSE; {not yet it doesn’t}
- rHandle:= RectHandle(Get1Resource('RECT', rListRectID));
- IF rHandle <> NIL THEN {get the stored list size}
- lView:= rHandle^^;
- SetRect(lBounds, 0, 0, 1, 0); {one dimentional list}
- SetPt(cSize, 0, 0); {List Mgr will find cell size}
- list:= LNew(lView, lBounds, cSize, 0, window, FALSE, FALSE, FALSE, TRUE);
- WITH window^.portRect DO
- maxListHeight:= bottom - top - (2 * kSndStdSpacing);
- IF maxListHeight < (lView.bottom - lView.top) THEN
- maxListHeight:= (lView.bottom - lView.top);
- IF list <> NIL THEN BEGIN
- newSize:= list^^.cellSize; {get the size of one cell}
- WHILE (newSize.v + list^^.cellSize.v) < maxListHeight DO
- newSize.v:= newSize.v + list^^.cellSize.v;
- LSize(newSize.h, newSize.v, list); {adjust for best scrolling}
- list^^.selFlags:= lOnlyOne; {single selections only}
- newDocPtr^.list:= list; {save the list handle}
- theErr:= InitSndList(newDocPtr); {initialize the list data}
- IF theErr = noErr THEN BEGIN
- SetPt(aCell, 0, 0); {by default, I will...}
- LSetSelect(TRUE, aCell, newDocPtr^.list); {select first item}
- LSetDrawingMode(TRUE, newDocPtr^.list);
- ShowWindow(WindowPtr(newDocPtr)); {get the show on the road}
- IF FailLowMemory(0) THEN {if I’m low on memory...}
- theErr:= memFullErr;
- END;
- END ELSE
- theErr:= nilHandleErr; {list handle was NIL}
- IF theErr <> noErr THEN BEGIN {could not create the list}
- ignore:= DoCloseWindow(window); {if not, close the window}
- newDocPtr:= NIL; {return NIL pointer too}
- END;
- END;
- CreateSoundDoc:= theErr; {return the error, if any}
- END; {CreateSoundDoc}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- PROCEDURE OpenSoundDoc(fNamePtr: StringPtr; wdVRefNum: INTEGER);
-
- {Believe it or not, but this turned out to be one of the more difficult
- routines to write. This was the best approach I could thing of to avoid
- serious problems. I don’t center the Standard File dialog because it
- wouldn’t be under the File menu where the user probably has the mouse. I
- use OpenRFPerm because it allows for permissions and doesn’t depend on the
- current directory. First problem with opening a resource file was to
- check if the file already being open. Read Tech Notes #116 and #185.
- GetFInfo seems to do the trick. At least it does report all files that
- are open on this machine. It may be incorrect for files on AppleShare
- opened by other machines. What I’m worried about is when opening a
- resource file for the second time on the same machine may return the
- previous opener’s resource map. This is very dangerous, and if the second
- opener calls CloseResFile there will be a crash. I want to be friendly
- and if the user tries to open the file the second time and I’ve already
- got it on the screen, then I’ll bring that window forward. To test for
- this, I compare the file’s name, DirID and vRefNum. The vRefNum is
- dynamic and needs to be converted into a volume name if I were to save
- this information for future use.
-
- There are a couple pitfalls, even with this method. The user can open the
- file, then move it to another directory. This same thing confuses all
- other applications I’ve tested. Opening a resource file will allocate a
- resource map handle into my heap, which may be large depending on the
- number of resources in that file. Additionally this loads all resources
- marked “preload.” So I set ResLoad to FALSE to prevent the preloading. I
- test if the amount of memory I believe my document will require is
- available after opening the resource file. If true, I test if there are
- any sound resources in that file and if not alert the user. After all
- this, create the new document. If after creating the new document I find
- that memory is too low, will close it and return an error. If it does
- fail, then close the file before anything else or there may not be enough
- memory to show the alert. All of these tests created a number of
- IF-THEN-ELSE blocks and became unyielding. C programmers get a break, so
- gimme me one too. I use the MPW Pascal EXIT.
-
- BUG NOTE: Don’t open a resource file that is already open. OpenResFile
- may return an existing resource map when it gets opWrErr from the file
- system. If this happens, the resource file will not be unique and this
- is very bad. Another problem is if I get a read-only path and someone
- else opens it for read/write. This is also very bad. Read Tech Notes
- #116 and #185 hint at this problem, but I think a more comprehensive one
- is in order.
-
- BUG NOTE: GetWDInfo fails with nsvErr if the working directory returned
- from Standard File is the root of an A/UX volume. I could work around
- this, but it would be dependant on the current version of A/UX. Read
- Tech Note #229. I believe this is also true for TOPS.
-
- VERSION 1.1: I'll open files that do not have snd resources in them.
- This allows users to open existing files and then record or paste a sound
- into it. I changed OpenRFPerm to HOpenResFile to avoid working
- directories and to be consistent with the rest of the sources. I
- separated the standard file code from OpenSndDoc to support opening
- documents being open from the Finder. This allows me to share the same
- routine to open files either from double clicking them in the Finder, or
- by the Standard File dialog. Removed the checking for low memory conditions
- since CreateSoundDoc is now doing this.}
-
- VAR
- fPBRec: ParamBlockRec;
- window: WindowPtr;
- dirID, procID: LONGINT;
- resFileRef,
- vRefNum,
- numSnds: INTEGER;
- theErr: OSErr;
-
- BEGIN
- WITH fPBRec DO BEGIN {prepare a paramBlock}
- ioCompletion:= NIL;
- ioNamePtr:= fNamePtr;
- ioVRefNum:= wdVRefNum;
- ioFVersNum:= 0;
- ioFDirIndex:= 0;
- END; {get file location}
- theErr:= GetWDInfo(wdVRefNum, vRefNum, dirID, procID);
- IF theErr <> noErr THEN BEGIN
- AlertUser(theErr, sStandardErr); {this fails at the root on A/UX}
- Exit(OpenSoundDoc);
- END;
- theErr:= PBGetFInfo(@fPBRec, NOT kFSAsynch); {fPBRec on stack, synch only}
- IF theErr = noErr THEN BEGIN
- IF BTst(fPBRec.ioFlAttrib, kResForkOpenBit) THEN BEGIN
- IF OpenByApp(vRefNum, dirID, fNamePtr, window) THEN
- SelectWindow(window) {I opened this one, select it}
- ELSE
- AlertUser(noErr, sCurInUseErr); {in use by someone else}
- Exit(OpenSoundDoc);
- END;
- END ELSE BEGIN
- AlertUser(theErr, sStandardErr); {PBGetFInfo failed}
- Exit(OpenSoundDoc);
- END;
- SetResLoad(FALSE); {don’t load any resources}
- resFileRef:= HOpenResFile(vRefNum, dirID, fNamePtr^, fsCurPerm);
- theErr:= ResError; {save error, if any}
- SetResLoad(TRUE); {restore ResLoad state}
- UseResFile(gAppResRef); {changes ResErr}
- IF resFileRef <> -1 THEN {error from OpenRFPerm?}
- theErr:= CreateSoundDoc(resFileRef, fNamePtr, vRefNum, dirID);
- IF theErr <> noErr THEN
- AlertUser(theErr, sNewDocErr); {couldn’t create new doc}
- END; {OpenSoundDoc}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- PROCEDURE NewSoundDoc;
-
- {VERSION 1.1: Create a new file for the user. I prefer to use the new
- HCreateResFile to avoid working directories, and to be consistent with
- the rest of the sources. Also, I need the real vRefNum and dirID to
- store in the sndDoc record. After creating the file, I set its Finder
- information to the proper type and creator. Finally, I get to open the
- new file. After this point everything is exactly like opening an
- existing file and creating a new sound document.}
-
- VAR
- msg: Str255;
- reply: SFReply;
- fndrInfo: FInfo;
- strHandle: StringHandle;
- dirID: LONGINT;
- procID: LONGINT;
- vRefNum: INTEGER;
- resFileRef: INTEGER;
- theErr: OSErr;
-
- BEGIN
- theErr:= noErr;
- strHandle:= StringHandle(Get1Resource('STR ', rPutFileMsg));
- IF strHandle <> NIL THEN
- msg:= strHandle^^ {save no name title}
- ELSE
- msg:= ''; {at least an empty string}
- SFPutFile(Point(kSFTopLeft), msg, '', NIL, reply);
-
- IF reply.good THEN BEGIN
- theErr:= GetWDInfo(reply.vRefNum, vRefNum, dirID, procID);
- HCreateResFile(vRefNum, dirID, reply.fName);
- theErr:= ResError;
- IF theErr = noErr THEN BEGIN
- theErr:= HGetFInfo(vRefNum, dirID, reply.fName, fndrInfo);
- IF theErr = noErr THEN BEGIN
- fndrInfo.fdType:= rSndAppDocType;
- fndrInfo.fdCreator:= rAppSignature;
- theErr:= HSetFInfo(vRefNum, dirID, reply.fName, fndrInfo);
- resFileRef := HOpenResFile(vRefNum, dirID, reply.fName, fsCurPerm);
- theErr:= ResError; {save error, if any}
- UseResFile(gAppResRef); {changes ResErr}
- IF resFileRef <> -1 THEN {error from HOpenResFile}
- theErr:= CreateSoundDoc(resFileRef, @reply.fName, vRefNum, dirID);
- END;
- END;
- END;
- IF theErr <> noErr THEN
- AlertUser(theErr, sNewDocErr); {return the error}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- FUNCTION SFFilter(p: CInfoPBPtr): BOOLEAN;
-
- {This is much more than what a typical file filter might do but I wanted
- the user to easily find files that contained snd resources. So, the
- global flag gSndFilesOnly is used to determine when to filter for such
- files. Otherwise, I show all files and allow the application to report to
- the user the file they’ve just tried to open doesn’t have any sounds to
- play with. Opening resource forks can be tricky if it’s already open. It
- would be very bad to use a resource fork that is already open by another
- application. The problem being that the Resource Manager doesn’t deal
- with multiple users. Read Tech notes #116 and #185. There’s also another
- problem. If a resource fork is opened by two applications and one closes
- the file then the entire resource fork may closed out from underneath the
- other application. I do, however, want to show the user files that
- contain sound resources even if they are currently open. I use
- HOpenResFile with read only permission, which will give me a unique
- resource reference. I look for a 'snd ' resource and then immediately
- close the file. Do not use a resource file opened with read only
- permission. Another reason to use HOpenResFile is to avoid a necessary
- working directory. I do not have one while in the file filter, but can
- use the DirID. Performing this search on each file is time consuming so I
- show a spinning cursor to show the user I’m working. Opening a resource
- fork may load resources mark preload. To avoid this, I call SetResLoad
- to FALSE. I bet you thought the Resource Manager was a free lunch. Ha!
- Read Tech Note #203 for other reasons not to play with resources.
-
- BUG NOTE: While debugging this routine using heap scramble, I found that
- OpenResFile would not open the requested file. I’m not sure if this is a
- problem with OpenResFile or SFGetFile.
-
- VERSION 1.1: The bug mentioned above was found. The problem is that the
- paramBlock happens to be a re-locatable block in the heap. Passing the
- de-reference ioNamePtr to HOpenResFile was de-referencing this
- re-locatable paramBlock. Since HOpenResFile moves memory, the namePtr
- would no longer valid and thus HOpenResFile would fail. Now I copy the
- name out of the paramBlock and use the local variable in HOpenResFile.
- This problem was fixed in System 7, which no longer passes a re-locatable
- block to the file filter. The new version allows for any resource file
- to be opened Because of this new feature, I only show the user files
- that have a resource fork.}
-
- CONST
- kShowIt = FALSE; {FALSE means I do not filter out...}
- kDoNotShowIt = TRUE; {the file and TRUE means that I do.}
-
- VAR
- resFName: Str255;
- oldTicks: LONGINT;
- resRef, curRes: INTEGER;
- theErr: OSErr;
-
- BEGIN
- oldTicks:= TickCount;
- SFFilter:= kDoNotShowIt; {don’t show anything until I say so}
- IF gSndFilesOnly THEN BEGIN
- curRes:= CurResFile;
- resFName:= p^.ioNamePtr^;
- SetResLoad(FALSE);
- resRef:= HOpenResFile(p^.ioVRefNum, LMGetCurDirStore, resFName, fsRdPerm);
- IF (resRef <> -1) THEN BEGIN
- UseResFile(resRef);
- IF Count1Resources('snd ') > 0 THEN
- SFFilter:= kShowIt; {hey, we found a sound in here}
- CloseResFile(resRef);
- END; {restore everything}
- SetResLoad(TRUE);
- UseResFile(curRes);
- END ELSE BEGIN
- IF p^.ioFlRLgLen > 0 THEN
- SFFilter:= kShowIt;
- END;
- RotateCursor(TickCount - oldTicks);
- END; {SFFilter}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Open}
- FUNCTION SFGetHook(MySFItem: INTEGER; dialog: DialogPtr): INTEGER;
-
- CONST
- kFirstTime = -1; {item is -1 first time into hook}
- reDrawList = 101; {returning 101 as item number will
- cause the file list to redraw}
- VAR
- kind: INTEGER;
- cntl: ControlHandle;
- r: Rect;
-
- BEGIN
- SFGetHook := MySFitem;
- GetDialogItem(dialog, rSndOnlyCheckBox, kind, Handle(cntl), r);
- CASE MySFItem OF
-
- kFirstTime: BEGIN
- IF gSndFilesOnly THEN
- SetControlValue(cntl, kCntlOn)
- ELSE
- SetControlValue(cntl, kCntlOff);
- END;
-
- rSndOnlyCheckBox: BEGIN
- IF GetControlValue(cntl) = kCntlOff THEN BEGIN
- SetControlValue(cntl, kCntlOn);
- gSndFilesOnly:= TRUE;
- END ELSE BEGIN
- SetControlValue(cntl, kCntlOff);
- gSndFilesOnly:= FALSE;
- END;
- SFGetHook:= reDrawList;
- END;
- END;
- END; {SFGetHook}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE GetSoundDoc;
-
- VAR
- reply: SFReply;
- fileTypes: SFTypeList; {not used, just a placeholder}
-
- {VERSION 1.1: This routine used to be inside of OpenSoundDoc. The
- standard file code was removed to here in order to support opening
- documents being open from the Finder. I've switched to using a constant
- for the topLeft point of the Standard File dialog. It saves a few bytes
- of code, so what the hell? I found a cosmetic problem with the cursor
- not being restored back to arrow after returning Standard File if there
- was an error dialog shown immediately. So, it is immediately set back to
- the arrow.}
-
- VAR
- ffUPP: FileFilterUPP;
- dhUPP: DlgHookUPP;
-
- BEGIN
- SpinCursor(0); {get the spinning cursor ready}
-
- ffUPP := NewFileFilterProc(@SFFilter);
- dhUPP := NewDlgHookProc(@SFGetHook);
- SFPGetFile(Point(kSFTopLeft), '',
- ffUPP, -1, fileTypes,
- dhUPP, reply, rSFPGetFileDLOG, NIL);
- DisposeRoutineDescriptor(ffUPP);
- DisposeRoutineDescriptor(dhUPP);
-
- SetCursor(qd.arrow);
- IF reply.good THEN
- OpenSoundDoc(@reply.fName, reply.VRefNum);
- END; {GetSoundDoc}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DrawAboutWindow(window: WindowPtr);
-
- {Draw the contents of the about window in response to an update event. At
- this point, BeginUpdate has been called which sets the window’s visRgn to
- clip drawing only where it needs to be done. I have some text to draw, an
- icon, a picture, and a default button with its outline. If I have a color
- icon handle I’ll call PlotCIcon. I offset the picture to draw to the
- right of the icon. The text will appear in a rectangle as large as the
- window, but below the icon and above the button. This allows me the
- change the text and if needed change the size of the window’s rectangle to
- compensate. I won’t have to recompile any code. Some people use dialogs
- because of this, but I’m demonstrating how it can be done without them. I
- use UpdateControls to avoid needless drawing that happens with DrawControls.
- It not only runs faster but doesn’t flicker.}
-
- VAR
- theRect: Rect;
-
- BEGIN
- PenNormal;
- SetRect(theRect, kIconLeft, kIconTop, kIconRight, kIconBot);
- IF AboutWPeek(window)^.iconIsColor THEN
- PlotCIcon(theRect, CIconHandle(AboutWPeek(window)^.appIcon))
- ELSE
- PlotIcon(theRect, AboutWPeek(window)^.appIcon);
-
- theRect:= PicHandle(AboutWPeek(window)^.appPict)^^.picFrame;
- OffsetRect(theRect, -theRect.left + kIconRight + kIconPictGap,
- -theRect.top + kIconTop);
- DrawPicture(PicHandle(AboutWPeek(window)^.appPict), theRect);
-
- WITH window^.portRect DO
- SetRect(theRect, left, kIconBot, right, bottom - kDafaultButSizeH);
- InsetRect(theRect, kMsgInset, kMsgInset);
- HLock(AboutWPeek(window)^.comment);
- TETextBox(Ptr(ORD(AboutWPeek(window)^.comment^) + 1),
- LENGTH(StringHandle(AboutWPeek(window)^.comment)^^),
- theRect, teJustCenter);
- HUnlock(AboutWPeek(window)^.comment);
-
- UpdateControls(window, window^.visRgn);
- DoButtonOutline(WindowPeek(window)^.controlList);
- END; {DrawAboutWindow}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoAbout;
-
- {First thing to do is call KillSound to stop any sound in progress and
- close the status window. If there’s enough memory available, I’ll play
- the about sound. This is sound is asynchronous and will automatically be
- disposed of when completed. There is an icon, taken from the bundle
- resources, and a picture. The text is taken from a string resource. It
- contains a couple variables just like ParamText would want. I get the
- current name of the application from the AppParms. This is the name of
- the application as specified by the user. I read in my 'vers' resource
- to get the current version to be displayed in the window. Read Tech Note
- 189 for more details on the vers resource. I think these are two
- important things to show in the about box. These two portions of the text
- are put into place with my own version of ParamText. I tried all of this
- with a standard dialog, but had trouble. Sometimes the color icon didn’t
- show up. Trying to center justify text with a statText item wasn’t really
- possible. On the Mac SE, for some unknown reason, TESetAlignment failed to
- center the textH of the dialog. I also found that this call would set the
- dialog’s text back to Chicago even after I had called TextFont. So, I
- gave up and did everything myself. This is a demonstration of how to
- create a dialog without using the Dialog Manager.}
-
- VAR
- verNum,
- userName: Str255;
- aboutPtr: Ptr;
- aboutPeek: AboutWPeek;
- control: ControlHandle;
- hndl: Handle;
- curVersion: VersRecHndl;
- refNum: INTEGER;
- theErr: OSErr;
- ignore: BOOLEAN;
-
- BEGIN
- KillSound;
- aboutPtr:= NewPtrClear(SIZEOF(AboutWindow));
- IF aboutPtr <> NIL THEN BEGIN
- aboutPeek:= AboutWPeek(GetCenteredWindow(rAboutWindow, aboutPtr, WindowPtr(-1)));
- SetWRefCon(WindowPtr(aboutPeek), rAboutWindow);
- curVersion:= VersRecHndl(Get1Resource('vers', 1));
- IF curVersion <> NIL THEN
- verNum:= curVersion^^.shortVersion {get version string}
- ELSE
- verNum:= ''; {at least initialize it}
- GetAppParms(userName, refNum, hndl);
- aboutPeek^.appPict:= Get1Resource('PICT', rAppPict);
- aboutPeek^.comment:= Get1Resource('STR ', rAboutText);
- aboutPeek^.appIcon:= NIL;
- aboutPeek^.iconIsColor:= TRUE;
- IF gMac.hasColorQD THEN
- aboutPeek^.appIcon:= Handle(GetCIcon(rMoofIcon));
- IF aboutPeek^.appIcon = NIL THEN BEGIN
- aboutPeek^.appIcon:= Get1Resource('ICON', rMoofIcon);
- aboutPeek^.iconIsColor:= FALSE;
- END;
- control:= GetNewControl(rAboutOkCntl, WindowPtr(aboutPeek));
-
- IF (aboutPeek^.appPict <> NIL) | (aboutPeek^.comment <> NIL)
- | (aboutPeek^.appIcon <> NIL) | (control <> NIL) THEN BEGIN
- HNoPurge(aboutPeek^.appPict); {must keep them around}
- HNoPurge(aboutPeek^.comment);
- HNoPurge(aboutPeek^.appIcon);
- MyParamText(StringHandle(aboutPeek^.comment), @userName, @verNum);
- IF NOT FailLowMemory(0) THEN
- theErr:= AsynchSndPlay(Get1Resource('snd ', rMoofSound));
- END ELSE BEGIN {couldn’t build window}
- DoErrorSound(1);
- ignore:= DoCloseWindow(WindowPtr(aboutPeek));
- END;
- END ELSE
- DoErrorSound(1);
- END; {DoAbout}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE PlaySelectedSnd(sndDoc: SndDocPeek; message: INTEGER);
-
- {Get the selected sound and pass it to AsynchSndPlay or HyperSndPlay. This
- depends on the normal flag. HyperCard’s method is not normal. This is
- the HyperCard version of playing a sound. It resamples any sound to
- middle C. If the resource is too large to fit in memory, I’ll get a NIL
- handle error from the Sound Unit. It might be nice to test for this
- before calling the Sound Unit and telling the user they’re too low on
- memory, but the unit is robust enough and reports the error. It’s
- important to call KillSound to dispose of any data that was
- allocated if an error were to occur.}
-
- VAR
- sndHandle: Handle;
- theErr: OSErr;
-
- BEGIN
- theErr:= GetSelection(sndDoc, sndHandle);
- IF theErr = noErr THEN BEGIN
- IF message = sPlayingMsg THEN
- theErr:= AsynchSndPlay(sndHandle)
- ELSE
- theErr:= HyperSndPlay(sndHandle);
- END;
- IF theErr = noErr THEN BEGIN
- sndDoc^.sndInUse:= TRUE; {this document has a snd in use}
- ShowStatusWindow(message);
- END ELSE BEGIN
- KillSound;
- AlertUser(theErr, sSoundErr);
- END;
- END; {PlaySelectedSnd}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE PlaySndSong(sndDoc: SndDocPeek; sndID: INTEGER);
-
- {Given a sound resource ID, this will get my song resource and call the
- Sound Unit to use that sampled sound and song to play a tune. This
- application will play one of two songs for sampled sounds. If any error
- is encountered, I call KillSound to dispose of all data.}
-
- VAR
- chan: SndChannelPtr;
- sndInst,
- sndSong: Handle;
- theErr: OSErr;
-
- BEGIN
- theErr:= GetSelection(sndDoc, sndInst);
- IF theErr = noErr THEN BEGIN
- sndSong:= Get1Resource('snd ', sndID);
- theErr:= ResError; {save any error}
- IF sndSong <> NIL THEN BEGIN
- theErr:= GetSampleChan(chan, kInitNone, sndInst);
- IF theErr = noErr THEN BEGIN
- sndDoc^.sndInUse:= TRUE; {this document has a snd in use}
- theErr:= PlaySong(chan, sndSong);
- END;
- END;
- END;
- IF theErr = noErr THEN BEGIN
- IF sndID = rScaleSnd THEN
- ShowStatusWindow(sScaleMsg)
- ELSE {I can play scales or a melody}
- ShowStatusWindow(sMelodyMsg);
- END ELSE BEGIN {catch any errors}
- KillSound;
- AlertUser(theErr, sSoundErr);
- END;
- END; {PlaySndSong}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE PlaySquareSong(sndID: INTEGER);
-
- {Given a sound resource ID, this will get my song resource and call the
- Sound Unit to use the squareWaveSynth and song to play a tune. I request
- the squareWaveSynth’s timbre (sounds like “tom burr”). If any error is
- encountered, I call KillSound to dispose of all data.}
-
- VAR
- theErr: OSErr;
- sndSong: Handle;
- chan: SndChannelPtr;
-
- BEGIN
- sndSong:= Get1Resource('snd ', sndID);
- IF (sndSong <> NIL) THEN BEGIN
- theErr:= GetSquareChan(chan, kPreferredTimbre);
- IF theErr = noErr THEN BEGIN
- theErr:= PlaySong(chan, sndSong);
- IF theErr = noErr THEN BEGIN
- IF sndID = rScaleSnd THEN
- ShowStatusWindow(sScaleMsg)
- ELSE {I play scales or a melody}
- ShowStatusWindow(sMelodyMsg);
- END;
- END;
- IF theErr <> noErr THEN BEGIN {catch any errors}
- KillSound;
- AlertUser(theErr, sSoundErr);
- END;
- END ELSE
- AlertUser(ResError, sResErr); {I’ll return the resource error}
- END; {PlaySquareSong}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE PlaySquareTimbres;
-
- {This is a demonstration of the squareWaveSynth’s tone qualities (or the lack
- there of). This simply loops through timbres sending alternating frequency and
- rest commands. Once all the sound commands have been sent, then I need to
- send a callBackCmd to signal the SoundUnit to dispose of the channel. Well,
- actually the SoundUnit will set a global flag that the application will be
- polling for later in the event loop. Once this happens, or if any errors
- are encountered along the way, KillSound will be called. One very
- disappointing aspect to changing the timbre is that the Mac Plus or SE
- cannot handle this while a sound is heard. The Apple Sound Chip can and
- if you wanted to remove the rests try this routine on a Mac II, for
- example, you can hear a continuous sound while the timbre changes. Try
- this on a Mac Plus and you’ll hear garbage. I’d show this myself, but how
- do I determine if the Mac has the ASC?
-
- BUG NOTE: There is problem when the final sound command is a freqDurationCmd.
- The frequency will continue to sound, looping forever, until a quietCmd is sent
- or the channel is disposed of. To prevent unwanted looping, I send a
- quietCmd after all frequencies.}
-
- VAR
- chan: SndChannelPtr;
- timbre: INTEGER;
- theErr: OSErr;
-
- BEGIN
- theErr:= GetSquareChan(chan, kPreferredTimbre);
- IF theErr = noErr THEN BEGIN
- ShowStatusWindow(sTimbresMsg);
- FOR timbre:= kSineWave TO kSquareWave DO BEGIN
- theErr:= SetSquareTimbre(chan, timbre, kWait);
- IF theErr = noErr THEN
- theErr:= SendFreqDur(chan, kOneSecond DIV 2, kOctave7 + Akey);
- IF theErr = noErr THEN
- theErr:= SendRest(chan, kOneSecond DIV 10);
- timbre:= timbre + 8; {skip a few more timbres}
- IF theErr <> noErr THEN {if there was an error...}
- timbre:= kSquareWave + 1; {get out of the loop}
- END;
- END;
- IF theErr = noErr THEN
- theErr:= SoundComplete(chan)
- ELSE BEGIN {catch any errors}
- KillSound;
- AlertUser(theErr, sSoundErr);
- END;
- END; {PlaySquareTimbres}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE PlayWaveScale;
-
- {This demonstrates the wave table synthesizers. First thing to do is
- obtain a wave cycle and the 'snd ' resource containing a song to be
- played. Then I’m ready to get the four wave channels and install the
- chosen wave. Once the wave is installed into the channel I can dispose of
- the memory used to create the wave, since the Sound Manager will copy it
- to its internal buffers. At this point I’m ready to play the song. It’s
- not easy to hear four wave synths playing the same frequencies with the
- same wave table. I was going to add a modifier to the channel that would
- transpose each channels frequency commands, but I found a bug.
-
- BUG NOTE: Installing a modifier to one of the wave table channels caused
- the channel to fail. My normal sequence of events is this: I synchronize
- the four channels, send all the note commands, end this with the
- callBackCmd, and finally release the channels to play their queues. When
- I added a modifier, the callBackCmd was the first command to be processed.
- This caused my completion routines to be called and before the channel
- made any sound it was disposed. I tried this without the callBackCmd and
- the channel never processed any commands.
-
- BUG NOTE: The wave table synthesizer is not working on a non-Apple Sound
- chip based Mac. At least not so far with any System 6.0x releases. This
- leaves the Mac Plus/SE without four tone polyphonic sound.}
-
- VAR
- chan1, chan2,
- chan3, chan4: SndChannelPtr;
- sndSong,
- waveHandle: Handle;
- waveTablePtr: Ptr;
- offSet: LONGINT;
- sndType,
- waveLength: INTEGER;
- theErr: OSErr;
-
- BEGIN
- IF (gMac.machineType IN [env512KE, envMacPlus, envSE])
- & (NOT HasNewSndMgr) THEN BEGIN
- AlertUser(badChannel, sWavesBroken); {waves not available}
- EXIT(PlayWaveScale); {we’re out of here}
- END;
- sndSong:= Get1Resource('snd ', rScaleSnd);
- IF sndSong <> NIL THEN BEGIN
- waveHandle:= Get1Resource('snd ', rTenorVox);
- theErr:= HoldSnd(waveHandle);
- IF theErr = noErr THEN BEGIN
- offSet:= GetSndDataOffset(waveHandle, sndType, waveLength);
- waveTablePtr:= Ptr(ORD4(waveHandle^) + offSet);
- theErr:= GetWaveChans(chan1, chan2, chan3, chan4);
- IF theErr = noErr THEN BEGIN
- theErr:= InstallWave(chan1, waveTablePtr, waveLength);
- IF theErr = noErr THEN BEGIN
- theErr:= InstallWave(chan2, waveTablePtr, waveLength);
- IF theErr = noErr THEN BEGIN
- theErr:= InstallWave(chan3, waveTablePtr, waveLength);
- IF theErr = noErr THEN
- theErr:= InstallWave(chan4, waveTablePtr, waveLength);
- END;
- END;
- END;
- HUnlock(waveHandle);
- HPurge(waveHandle);
- IF theErr = noErr THEN
- theErr:= Play4Waves(chan1, chan2, chan3, chan4,
- sndSong, sndSong, sndSong, sndSong);
- IF theErr = noErr THEN
- ShowStatusWindow(sScaleMsg)
- ELSE BEGIN {catch any sound unit errors}
- KillSound;
- AlertUser(theErr, sSoundErr);
- END;
- END ELSE
- AlertUser(theErr, sResErr); {couldn’t get waveHandle}
- END ELSE
- AlertUser(ResError, sResErr); {couldn’t get song}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION GetSndSongs(sndSongID1, sndSongID2, sndSongID3, sndSongID4: INTEGER;
- VAR sndSong1, sndSong2, sndSong3, sndSong4: Handle): OSErr;
-
- {This is a utility routine used to obtain the four snd resources that
- contain parts of a song. Nothing much to do other then get the resource
- and do error checking.}
-
- BEGIN
- GetSndSongs:= noErr; {initialize result}
- sndSong1:= Get1Resource('snd ', sndSongID1); {get all the snds}
- IF sndSong1 <> NIL THEN BEGIN
- sndSong2:= Get1Resource('snd ', sndSongID2);
- IF sndSong2 <> NIL THEN BEGIN
- sndSong3:= Get1Resource('snd ', sndSongID3);
- IF sndSong3 <> NIL THEN
- sndSong4:= Get1Resource('snd ', sndSongID4);
- END;
- END;
- IF (sndSong1 = NIL) | (sndSong2 = NIL)
- | (sndSong3 = NIL) | (sndSong4 = NIL) THEN
- GetSndSongs:= ResError; {return resource error}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION InstallWaveSnds(chan1, chan2, chan3, chan4: SndChannelPtr;
- waveID1, waveID2, waveID3, waveID4: INTEGER): OSErr;
-
- {This routine is a utility I’m using to prepare four wave table channels.
- First thing to do is obtain a wave table data I stored in a set of 'snd '
- resources. Then I’m ready to install these wave tables into the four wave
- channels. Once the waves are installed into the channel I can dispose of
- the memory used to create each wave, since the Sound Manager will copy
- them to its internal buffers. At this point the channels are ready to
- play sounds.}
-
- VAR
- waveSnd1, waveSnd2,
- waveSnd3, waveSnd4: Handle;
- wavePtr: Ptr;
- offSet: LONGINT;
- waveLgth, sndType: INTEGER;
- theErr: OSErr;
-
- BEGIN
- waveSnd1:= Get1Resource('snd ', waveID1); {get em and hold em down}
- theErr:= HoldSnd(waveSnd1);
- IF theErr = noErr THEN BEGIN
- waveSnd2:= Get1Resource('snd ', waveID2);
- theErr:= HoldSnd(waveSnd2);
- IF theErr = noErr THEN BEGIN
- waveSnd3:= Get1Resource('snd ', waveID3);
- theErr:= HoldSnd(waveSnd3);
- IF theErr = noErr THEN BEGIN
- waveSnd4:= Get1Resource('snd ', waveID4);
- theErr:= HoldSnd(waveSnd4);
- END;
- END;
- END;
- InstallWaveSnds:= theErr; {return the error}
- IF theErr <> noErr THEN
- EXIT(InstallWaveSnds); {we’re out of here}
- {catch the waves}
- offSet:= GetSndDataOffset(waveSnd1, sndType, waveLgth);
- wavePtr:= Ptr(ORD4(waveSnd1^) + offSet);
- theErr:= InstallWave(chan1, wavePtr, waveLgth);
- IF theErr = noErr THEN BEGIN
- offSet:= GetSndDataOffset(waveSnd2, sndType, waveLgth);
- wavePtr:= Ptr(ORD4(waveSnd2^) + offSet);
- theErr:= InstallWave(chan2, wavePtr, waveLgth);
- IF theErr = noErr THEN BEGIN
- offSet:= GetSndDataOffset(waveSnd3, sndType, waveLgth);
- wavePtr:= Ptr(ORD4(waveSnd3^) + offSet);
- theErr:= InstallWave(chan3, wavePtr, waveLgth);
- IF theErr = noErr THEN BEGIN
- offSet:= GetSndDataOffset(waveSnd4, sndType, waveLgth);
- wavePtr:= Ptr(ORD4(waveSnd4^) + offSet);
- theErr:= InstallWave(chan4, wavePtr, waveLgth);
- END;
- END;
- END;
- HUnlock(waveSnd1); {done with resources}
- HPurge(waveSnd1);
- HUnlock(waveSnd2);
- HPurge(waveSnd2);
- HUnlock(waveSnd3);
- HPurge(waveSnd3);
- HUnlock(waveSnd4);
- HPurge(waveSnd4);
- InstallWaveSnds:= theErr; {return the error}
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE PlayWaveMelody;
-
- {This is the demonstration of the wave table synthesizers. This will get
- four snd resources that contain parts to a song. Then I get the four wave
- table channels. With these four channels, I install two other snd
- resources that contain wave table data. Once the four songs, four
- channels, and two wave tables are ready then I play the song.
-
- BUG NOTE: The wave table synthesizer is not working on a non-Apple Sound
- chip based Mac. At least not so far with any System 6.0x release. This
- leaves the Mac Plus/SE without four tone polyphonic sound.}
-
- VAR
- sndSong1, sndSong2,
- sndSong3, sndSong4: Handle;
- chan1, chan2,
- chan3, chan4: SndChannelPtr;
- theErr: OSErr;
-
- BEGIN
- theErr:= noErr;
- IF (gMac.machineType IN [env512KE, envMacPlus, envSE])
- & (NOT HasNewSndMgr) THEN BEGIN
- AlertUser(badChannel, sWavesBroken); {waves not available}
- EXIT(PlayWaveMelody); {we’re out of here}
- END;
- theErr:= GetSndSongs(rMelodyPart1, rMelodyPart2, rMelodyPart3, rMelodyPart4,
- sndSong1, sndSong2, sndSong3, sndSong4);
- IF theErr <> noErr THEN BEGIN
- AlertUser(theErr, sResErr); {return the error}
- EXIT(PlayWaveMelody);
- END;
- theErr:= GetWaveChans(chan1, chan2, chan3, chan4);
- IF theErr = noErr THEN BEGIN
- theErr:= InstallWaveSnds(chan1, chan2, chan3, chan4,
- rWaveMelody, rWaveHarmony, rWaveHarmony, rWaveHarmony);
- IF theErr = noErr THEN
- theErr:= Play4Waves(chan1, chan2, chan3, chan4,
- sndSong1, sndSong2, sndSong3, sndSong4);
- END;
- IF theErr = noErr THEN
- ShowStatusWindow(sMelodyMsg)
- ELSE BEGIN {catch any errors}
- KillSound;
- AlertUser(theErr, sSoundErr);
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE PlayWaveSATB;
-
- {This is the demonstration of the wave table synthesizers. This will get
- four snd resources that contain parts to a song. Then I get the four wave
- table channels. With these four channels, I install four other snd resources
- that contain wave table data. Once the four songs, four channels, and
- four wave tables are ready then I play the song. By the way, SATB stands
- for Soprano, Alto, Tenor, and Bass. It’s standard music-speak.
-
- BUG NOTE: The wave table synthesizer is not working on a non-Apple Sound
- chip based Mac. At least not so far with any System 6.0x release. This
- leaves the Mac Plus/SE without four tone polyphonic sound.}
-
- VAR
- sndSong1, sndSong2,
- sndSong3, sndSong4: Handle;
- chan1, chan2,
- chan3, chan4: SndChannelPtr;
- theErr: OSErr;
-
- BEGIN
- theErr:= noErr;
- IF (gMac.machineType IN [env512KE, envMacPlus, envSE])
- & (NOT HasNewSndMgr) THEN BEGIN
- AlertUser(badChannel, sWavesBroken); {waves not available}
- EXIT(PlayWaveSATB); {we’re out of here}
- END;
- theErr:= GetSndSongs(rCounterPt1, rCounterPt2, rCounterPt3, rCounterPt4,
- sndSong1, sndSong2, sndSong3, sndSong4);
- IF theErr <> noErr THEN BEGIN
- AlertUser(theErr, sResErr); {return the error}
- EXIT(PlayWaveSATB);
- END;
- theErr:= GetWaveChans(chan1, chan2, chan3, chan4);
- IF theErr = noErr THEN BEGIN
- theErr:= InstallWaveSnds(chan1, chan2, chan3, chan4,
- rSopranoVox, rAltoVox, rTenorVox, rBassVox);
- IF theErr = noErr THEN
- theErr:= Play4Waves(chan1, chan2, chan3, chan4,
- sndSong1, sndSong2, sndSong3, sndSong4);
- END;
- IF theErr = noErr THEN
- ShowStatusWindow(sCounterPtMsg)
- ELSE BEGIN {catch any errors}
- KillSound;
- AlertUser(theErr, sSoundErr);
- END;
- END;
-
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DefaultOutline(window: WindowPtr; theItem: INTEGER);
-
- {VERSION 1.1: This is just one of the reasons I hate the Dialog Manager.
- I needed a simple dialog to ask the user for a name for their new sound.
- To do this, I only needed a simple dialog with two buttons and an
- editable text item. Since the ModalDialog will not draw an outline
- around the default item, I then needed an userItem. This is the really
- stupid part. The userItem is only there just to get a chance to draw an
- outline around the default button. It has no other purpose. It is not
- visible, not does it get any user interaction what so ever. It's just a
- pain in the ass.}
-
- VAR
- itemHndl: Handle;
- itemRect: Rect;
- kind: INTEGER;
-
- BEGIN
- GetDialogItem(DialogPtr(window), ok, kind, itemHndl, itemRect);
- DoButtonOutline(ControlHandle(itemHndl));
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE GetSndName(VAR sndName: Str255);
-
- {VERSION 1.1: Show a dialog asking the user to name their new sound
- resource. I need to outline the default button, so I have to install an
- userItem setting its drawing procedure to do the outline around a
- different item altogether. The Dialog Manager really bugs me.}
-
- VAR
- dialog: DialogPtr;
- itemHndl: Handle;
- itemRect: Rect;
- kind: INTEGER;
- theItem: INTEGER;
-
- BEGIN
- dialog:= GetCenteredDialog(rGetNameDLOG, NIL, WindowPtr(-1));
- GetDialogItem(dialog, rUserItem, kind, itemHndl, itemRect);
- SetDialogItem(dialog, rUserItem, kind, @DefaultOutline, itemRect);
- REPEAT
- ModalDialog(NIL, theItem);
- UNTIL (theItem = ok) | (theItem = cancel);
- IF theItem = ok THEN BEGIN
- GetDialogItem(dialog, rNameItem, kind, itemHndl, itemRect);
- GetDialogItemText(itemHndl, sndName);
- END ELSE
- sndName:= '';
- DisposeDialog(dialog);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION AddSnd(sndDoc: SndDocPeek; sndNamePtr: StringPtr; sndHndl: Handle): OSErr;
-
- {VERSION 1.1: This routine adds the given snd resource to the file and
- document. First thing to do is find a proper resource ID for the 'snd '.
- There is a reserved range for them, didn't you read the documentation?
- Once this new resource is added then I update the file and re-build the
- list of sounds. This is necessary since the list must be in the same
- order as the sounds are in the file. I ignore the error returned by
- InitSndList, since the chances of it failing at this point are slim. If
- anything is wrong with the list, then the rest of this application is
- robust enough to handle a resource problem. The user would have to close
- the document and attempt to open it again. If adding the resources
- works, but updating the file fails then I will remove it. This keeps the
- document consistent with the file.}
-
- VAR
- resID: INTEGER;
- theErr: OSErr;
- ignore: INTEGER;
-
- BEGIN
- UseResFile(sndDoc^.resFile); {put our resource in the right file}
- REPEAT
- resID:= Unique1ID('snd ');
- UNTIL resID > kSystemSndRange;
- AddResource(sndHndl, 'snd ', resID, sndNamePtr^);
- theErr:= ResError;
- UseResFile(gAppResRef); {restore our resource file}
- IF theErr = noErr THEN BEGIN
- UpdateResFile(sndDoc^.resFile); {update the file}
- theErr:= ResError;
- IF theErr = noErr THEN BEGIN
- ignore:= FlushVol(nil, sndDoc^.vRefNum);
- LDelRow(0, 0, sndDoc^.list); {delete all the rows}
- ignore:= InitSndList(sndDoc); {re-create the list}
- SelectSndCell(sndDoc, resID); {select the new snd}
- END ELSE
- RemoveResource(sndHndl); {couldn't add it, update failed}
- END;
- AddSnd:= theErr;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE ClearSnd(sndDoc: SndDocPeek);
-
- {VERSION 1.1: This routine has one tricky aspect involving the Resource
- Manager and removing a resource. There's a catch. I want the document
- to always reflect the contents of the file on disk. In other words,
- InitSndList depends on UpdateResFile returning noErr. If the file is
- locked, then removing a resource shouldn't be allowed but RemoveResource
- will return noErr even if the file cannot be updated. This is a
- "feature" since the user may unlock the volume after removing the
- resource and then UpdateResFile would work. If RemoveResource succeeds,
- but UpdateResFile fails then the resource map in memory will be
- inconsistent with what's on disk. If the user then closed the document
- and opened it again, the resource they just removed would magically still
- be there. The basic problem is that it is difficult to determine if a
- given file will really allow write access. There's at least three
- situations to check: a locked file, a locked volume, or an AppleShare
- folder privileges issue. The ideal, and probably the best, solution
- would be to deselect the edit commands from the user for a read-only
- file. This would involve lots of File Manager calls every time the user
- selects a menu command. (If you thought that GetFCBInfo would tell you
- this, you're wrong. You can have write permission to a file that is on a
- locked volume.) Additionally, the user needs to see that the reason the
- edit menu doesn't work is because the file is read-only which would
- require another feature to be added with lots more code. This is why I
- call ChangedResource before any other Resource Manager calls.
- ChangedResource will determine if the resource can be written to disk.
- If not, then it returns an error which is exactly what I wanted to know
- in the first place. I want to flush the cache to keep the disk's
- resource map consistent with the resource data. Otherwise, a crash could
- occur the resource fork might be damaged and the file has to be repaired
- (if possible) or deleted. I'm ignoring the InitSndList result. At this
- stage, there's only a slim chance the list couldn't be re-built. If it
- does fail, the file should be closed.
-
- A tip to the reader: dispose of as much memory as possible before calling
- UpdateResFile. This makes it faster. UpdateResFile will not purge any
- memory, but only attempts to use the available space. If there's little
- free space then UpdateResFile will run really slow.}
-
- VAR
- resFName: Str255;
- sndHndl: Handle;
- theErr, openErr: OSErr;
- ignore: INTEGER;
- closed: BOOLEAN;
-
- BEGIN
- theErr:= GetSelection(sndDoc, sndHndl); {get the resource to remove}
- IF theErr = noErr THEN BEGIN
- ChangedResource(sndHndl); {can we change the file?}
- theErr:= ResError; {save the error result}
- IF theErr = noErr THEN BEGIN
- UseResFile(sndDoc^.resFile); {use the right resource file}
- RemoveResource(sndHndl); {remove that sucker}
- theErr:= ResError; {save the error result}
- UseResFile(gAppResRef); {restore our resource file}
- IF theErr = noErr THEN BEGIN
- DisposeHandle(sndHndl); {get rid of the memory}
- UpdateResFile(sndDoc^.resFile); {update the file}
- theErr:= ResError; {save the error result}
- IF theErr = noErr THEN
- ignore:= FlushVol(nil, sndDoc^.vRefNum);
- LDelRow(0, 0, sndDoc^.list); {delete all the rows}
- ignore:= InitSndList(sndDoc); {re-create the list}
- SelectNextCell(sndDoc^.list, TRUE); {select the first cell}
- ActivateSndCntls(sndDoc); {may not be any sounds left}
- END;
- END;
- END;
- IF theErr <> noErr THEN
- AlertUser(theErr, sEditErr);
- END; {ClearSnd}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoRecordSound(sndDoc: SndDocPeek);
-
- {VERSION 1.1: This routine will create a handle and record sound into it.
- A new sound will prompt the user for a name to add it to the document.
- The Sound Input Manager will re-size this handle to be as small as
- possible to contain only the samples recorded. You can pass sndHandl =
- NIL to SndRecord, which will cause the Sound Input Manager to create a
- handle for you. If adding the new sound is successful, the new resource
- handle is marked purgeable, because I only use them temporally and they
- tend to be large. I have to set the sndHandle variable to NIL in this
- case since it is now belongs to the Resource Manager and I don't want to
- dispose of it in the error handling code. The choice of recording
- quality wasn't given to the user. Typically, users would only be
- confused by the question of "what compression ratio do you prefer?" As
- a power user option, it would be nice to let the user set the rate. Be
- careful since this is a user interface issue, and Apple expects to see
- lots of Sound Input features in applications.}
-
- VAR
- sndName: Str255;
- total: LONGINT;
- contig: LONGINT;
- sndHandle: Handle;
- theErr: OSErr;
-
- BEGIN
- KillSound;
- PurgeSpace(total, contig);
- sndHandle:= NewHandle(contig - kMinSpace);
- IF sndHandle <> NIL THEN BEGIN
- theErr:= SndRecord(NIL, Point(kSFTopLeft), siBestQuality, SndListHandle(sndHandle));
- IF theErr = noErr THEN BEGIN
- GetSndName(sndName);
- theErr:= AddSnd(sndDoc, @sndName, sndHandle);
- IF theErr = noErr THEN BEGIN
- HPurge(sndHandle);
- sndHandle:= NIL;
- END;
- END;
- END ELSE
- theErr:= MemError;
- IF sndHandle <> NIL THEN
- DisposeHandle(sndHandle);
- IF (theErr <> noErr) & (theErr <> userCanceledErr) THEN
- AlertUser(theErr, sSoundErr);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE AdjustMenus;
-
- {Enable and disable menus based on the current state. The user can only
- select enabled menu items so I set up all the menu items before calling
- MenuSelect or MenuKey, since these are the only times that a menu item
- can be selected. Note that MenuSelect is also the only time the user will
- see menu items. This approach to deciding what enable/disable state a menu
- item has the advantage of concentrating all the decision making in one
- routine, as opposed to being spread throughout the application. Other
- application designs may take a different approach that may or may not be
- just as valid.}
-
- VAR
- menu: MenuHandle;
- window: WindowPtr;
- allowEdit: BOOLEAN;
-
- BEGIN
- window:= FrontWindow;
-
- menu:= GetMenuHandle(mFile); {the File menu and items}
- IF IsDAWindow(window) | IsDocWindow(window) THEN
- EnableItem(menu, iClose)
- ELSE
- DisableItem(menu, iClose);
-
- menu:= GetMenuHandle(mEdit); {the Edit menu and items}
- allowEdit:= IsDAWindow(window);
- IF allowEdit THEN {check the undo item}
- EnableItem(menu, iUndo)
- ELSE
- DisableItem(menu, iUndo);
-
- IF IsDocWindow(window) THEN BEGIN {handle the other edit items}
- allowEdit:= HasSelection(SndDocPeek(window)) | allowEdit;
- EnableItem(menu, iPaste);
- END ELSE
- DisableItem(menu, iPaste);
-
- IF allowEdit THEN BEGIN
- EnableItem(menu, iCut);
- EnableItem(menu, iCopy);
- EnableItem(menu, iClear);
- END ELSE BEGIN
- DisableItem(menu, iCut);
- DisableItem(menu, iCopy);
- DisableItem(menu, iClear);
- END;
- END; {AdjustMenus}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE CopySnd(sndDoc: SndDocPeek);
-
- {VERSION 1.1: Get the currently selected item and copy it to the
- clipboard. I want to also save the name for this resource, so I add a
- string type to the clipboard as well.}
-
- VAR
- sndName: Str255;
- rType: ResType;
- scrapLen: LONGINT;
- id: INTEGER;
- sndHndl: Handle;
- theErr: OSErr;
-
- BEGIN
- theErr:= GetSelection(sndDoc, sndHndl);
- IF theErr = noErr THEN BEGIN
- GetResInfo(sndHndl, id, rType, sndName);
- scrapLen:= ZeroScrap; {ignoring the result}
- theErr:= PutScrap(LENGTH(sndName) + 1, 'STR ', @sndName);
- HLock(sndHndl);
- theErr:= PutScrap(GetHandleSize(sndHndl), 'snd ', sndHndl^);
- HUnlock(sndHndl);
- HPurge(sndHndl);
- END;
- IF theErr <> noErr THEN
- AlertUser(theErr, sEditErr);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE CutSnd(sndDoc: SndDocPeek);
-
- {VERSION 1.1: Copy the selection and then delete it.}
-
- BEGIN
- CopySnd(sndDoc);
- ClearSnd(sndDoc);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE PasteSnd(sndDoc: SndDocPeek);
-
- {VERSION 1.1: This routine will create a new resource into the file
- containing the clipboard's sound data. A name is attempted to be found
- by first checking for the string type in the scrap. This application
- will always put both a string and a sound together on the clipboard. But
- if the user copied a sound from the Sound cdev, ResEdit, or by some other
- method then I'll ask the user for a new name. If adding the new resource
- is successful, then I mark the resource as purgeable and set the sndHndl
- variable to NIL. The Resource Manager then owns the handle and I don't
- want my error handling to dispose of this new handle.}
-
- VAR
- sndName: Str255;
- sndNameHndl: StringHandle;
- offset: LONGINT;
- scrapLen: LONGINT;
- sndHndl: Handle;
- theErr: OSErr;
-
- BEGIN
- sndHndl:= NewHandle(0);
- sndNameHndl:= NewString('');
- theErr:= MemError;
- scrapLen:= GetScrap(Handle(sndNameHndl), 'STR ', offset);
- scrapLen:= GetScrap(sndHndl, 'snd ', offset);
- IF scrapLen > 0 THEN BEGIN
- IF sndNameHndl <> NIL THEN
- sndName:= sndNameHndl^^
- ELSE
- sndName:= '';
- IF sndName = '' THEN
- GetSndName(sndName);
- theErr:= AddSnd(sndDoc, @sndName , sndHndl);
- IF theErr = noErr THEN BEGIN
- HPurge(sndHndl);
- sndHndl:= NIL; {done with handle}
- ActivateSndCntls(sndDoc);
- END;
- END ELSE
- theErr:= scrapLen;
-
- IF sndHndl <> NIL THEN
- DisposeHandle(sndHndl);
- IF sndNameHndl <> NIL THEN
- DisposeHandle(Handle(sndNameHndl));
- IF theErr <> noErr THEN
- AlertUser(theErr, sEditErr);
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoMenuCommand(menuResult: LONGINT);
-
- {This is called when an item is chosen from the menu bar (after calling
- MenuSelect or MenuKey). It performs the right operation for each command.
- It is good to have both the result of MenuSelect and MenuKey go to
- one routine like this to keep everything organized.
-
- VERSION 1.1: Supporting the edit commands for snd resources.}
-
- VAR
- window: WindowPtr;
- menuID, menuItem, {resource ID and item of the selected menu}
- daRefNum: INTEGER;
- daName: Str255;
- ignore: BOOLEAN;
-
- BEGIN
- menuID:= HiWord(menuResult); {use MPW’s built-ins for efficiency...}
- menuItem:= LoWord(menuResult); {to get menu item number and menu number}
- CASE menuID OF
-
- mApple:
- CASE menuItem OF
- iAbout: {bring up alert for About}
- DoAbout;
- OTHERWISE BEGIN {all non-About items in this menu are DAs}
- GetMenuItemText(GetMenuHandle(mApple), menuItem, daName);
- daRefNum:= OpenDeskAcc(daName);
- END;
- END;
-
- mFile:
- CASE menuItem OF
- iNew:
- NewSoundDoc;
- iOpen:
- GetSoundDoc;
- iClose:
- ignore:= DoCloseWindow(FrontWindow); {I don’t care if cancelled}
- iQuit:
- Terminate;
- END;
-
- mEdit: BEGIN {call SystemEdit for DA editing & MultiFinder}
- IF NOT SystemEdit(menuItem - 1) THEN BEGIN {since I don’t do any editing}
- window:= FrontWindow;
- IF IsDocWindow(window) THEN BEGIN
- CASE menuItem OF
-
- iCut:
- CutSnd(SndDocPeek(window));
-
- iCopy:
- CopySnd(SndDocPeek(window));
-
- iPaste:
- PasteSnd(SndDocPeek(window));
-
- iClear:
- ClearSnd(SndDocPeek(window));
-
- END;
- END;
- END;
- END;
-
- mDemos:
- CASE menuItem OF
-
- iSquareScale:
- PlaySquareSong(rScaleSnd);
-
- iSquareMelody:
- PlaySquareSong(rMelodyPart1);
-
- iSquareTimbre:
- PlaySquareTimbres;
-
- iWaveScale:
- PlayWaveScale;
-
- iWaveMelody:
- PlayWaveMelody;
-
- iWaveSATB:
- PlayWaveSATB;
-
- END; {CASE menuItem OF}
-
- END; {CASE menuID OF}
- HiliteMenu(0); {unhighlight what MenuSelect or MenuKey hilited}
- END; {DoMenuCommand}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DrawSndWindow(window: WindowPtr);
-
- {Draw the contents of the application window in response to an update
- event. At this point, BeginUpdate has been called which sets the window’s
- visRgn to clip drawing only where it needs to be done. I have the
- controls to draw, a list to update, and a default button to outline. I
- use UpdateControls to avoid needless drawing that happens with DrawControls.
- It not only runs faster but doesn’t flicker.}
-
- VAR
- control: ControlHandle;
- theRect: Rect;
-
- BEGIN
- PenNormal;
- LUpdate(window^.VisRgn, SndDocPeek(window)^.list); {update list}
- theRect:= SndDocPeek(window)^.list^^.rView; {frame the list}
- theRect.right:= theRect.right + kScrollBarAdjust;
- InsetRect(theRect, kListFrameInset, kListFrameInset);
- FrameRect(theRect);
- UpdateControls(window, window^.visRgn); {update controls}
- control:= WindowPeek(window)^.controlList; {draw button outline}
- WHILE control <> NIL DO BEGIN
- IF GetControlReference(control) = rPlaySndCntl THEN
- DoButtonOutline(control);
- control:= control^^.nextControl;
- END;
- END; {DrawWindow}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoKeyDown(key: CHAR; window: WindowPtr);
-
- {When the user types a key, I check if it is one that I’m looking for.
- This routine only handles the non-command key events. Here I’m looking
- for a arrow key or the return and enter keys for the default button.
-
- VERSION 1.1: The enter and return keys only work if there is a selection.
- Now supporting the backspace and delete keys which clear the selection.}
-
- VAR
- control: ControlHandle;
- ignore: BOOLEAN;
- kind: INTEGER;
- r: Rect;
-
- BEGIN
- CASE GetWRefCon(window) OF
-
- rSoundWindow: BEGIN
- CASE key OF
-
- kEnterKey, kReturnKey: BEGIN
- IF HasSelection(SndDocPeek(window)) THEN BEGIN
- control:= WindowPeek(window)^.controlList;
- WHILE control <> NIL DO BEGIN {find the default button}
- IF GetControlReference(control) = rPlaySndCntl THEN
- SelectButton(control); {here it is}
- control:= control^^.nextControl;
- END;
- PlaySelectedSnd(SndDocPeek(window), sPlayingMsg);
- END;
- END;
-
- kUpArrow: BEGIN
- SelectNextCell(SndDocPeek(window)^.list, FALSE);
- ActivateSndCntls(SndDocPeek(window));
- END;
-
- kDownArrow: BEGIN
- SelectNextCell(SndDocPeek(window)^.list, TRUE);
- ActivateSndCntls(SndDocPeek(window));
- END;
-
- kBackspace, kDelete:
- ClearSnd(SndDocPeek(window));
- END;
- END; {rSoundWindow}
-
- rStatusWindow:
- IF key IN [kEnterKey, kReturnKey, kEscape] THEN BEGIN
- SelectButton(WindowPeek(window)^.controlList);
- KillSound;
- END;
-
- rAboutWindow:
- IF key IN [kEnterKey, kReturnKey, kEscape] THEN BEGIN
- SelectButton(WindowPeek(window)^.controlList);
- ignore:= DoCloseWindow(window);
- END;
- END; {CASE GetWRefCon(window)}
- END; {DoKeyDown}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- FUNCTION ListClick(sndDoc: SndDocPeek; event: EventRecord): BOOLEAN;
-
- {Given a document, this will handle any clicking in the list. The mouse
- point (event.where) is expected to have been adjusted to the local
- coordinates of the window. If this routine does handle the event, then
- I return TRUE.
-
- BUG NOTE: LClick will return a double-click even if no cell was selected.
- So I have to test for the last click being in a real cell. If not, then
- I will not process the double click. Also, there is another bug in the
- List Manager. It will not always deselect the currently selected cell when
- the user clicks outside of the data bounds. In other words, sometimes
- my list would show 4 items when the list has room to show 8. If the user
- clicked in the bottom area of the list (below the last item) the List
- Manager should deselect any items. It doesn’t all the time, just sometimes.
- The only real solution would be to write a new LClick.}
-
- VAR
- aCell: Cell;
- listRect: Rect;
-
- BEGIN
- listRect:= sndDoc^.list^^.rView;
- listRect.right:= listRect.right + kScrollBarAdjust;
- IF PtInRect(event.where, listRect) THEN BEGIN
- IF LClick(event.where, event.modifiers, sndDoc^.list) THEN BEGIN
- aCell:= LLastClick(sndDoc^.list);
- IF PtInRect(aCell, sndDoc^.list^^.dataBounds) THEN
- PlaySelectedSnd(sndDoc, sPlayingMsg);
- END;
- ActivateSndCntls(sndDoc);
- ListClick:= TRUE; {I handled the event}
- END ELSE
- ListClick:= FALSE;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoSndDocClick(sndDoc: SndDocPeek; event: EventRecord);
-
- {This is called when a mouse-down event occurs in the content of my
- application windows. First thing to check for is a click in the list.
- If not a list click, then find what control may have been clicked in and
- handle that.
-
- VERSION 1.1: Handle the record button.}
-
- VAR
- control: ControlHandle;
-
- BEGIN
- SetPort(GrafPtr(sndDoc));
- GlobalToLocal(event.where);
- IF NOT ListClick(sndDoc, event) THEN BEGIN
- IF FindControl(event.where, WindowPtr(sndDoc), control) <> 0 THEN BEGIN
- IF TrackControl(control, event.where, NIL) <> 0 THEN BEGIN
- CASE GetControlReference(control) OF
-
- rPlaySndCntl:
- PlaySelectedSnd(sndDoc, sPlayingMsg);
-
- rHyperPlayCntl:
- PlaySelectedSnd(sndDoc, sHyperMsg);
-
- rPlayScaleCntl:
- PlaySndSong(sndDoc, rScaleSnd);
-
- rMelodyCntl:
- PlaySndSong(sndDoc, rMelodyPart1);
-
- rStopCntl:
- KillSound;
-
- rRecordCntl:
- DoRecordSound(sndDoc);
-
- END; {CASE GetControlReference(control)}
- END; {IF TrackControl}
- END; {IF FindControl}
- END; {IF NOT ListSelect}
- END; {DoSndDocClick}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoStatClick(statWindow: StatWindowPeek; event: EventRecord);
-
- {The status window has a stop button. This will stop any sound in
- progress. So, if the user clicks in my status window I need to check
- for this.}
-
- VAR
- control: ControlHandle;
-
- BEGIN
- SetPort(GrafPtr(statWindow));
- GlobalToLocal(event.where);
- IF FindControl(event.where, WindowPtr(statWindow), control) <> 0 THEN
- IF TrackControl(control, event.where, NIL) <> 0 THEN
- KillSound;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoAboutClick(window: WindowPtr; event: EventRecord);
-
- {The about window has a single OK button. If the users clicks in it, then
- close the window.}
-
- VAR
- control: ControlHandle;
- ignore: BOOLEAN;
-
- BEGIN
- SetPort(window);
- GlobalToLocal(event.where);
- IF FindControl(event.where, WindowPtr(window), control) <> 0 THEN BEGIN
- IF TrackControl(control, event.where, NIL) <> 0 THEN
- ignore:= DoCloseWindow(window);
- END;
- END;
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoUpdate(window: WindowPtr);
-
- {This is called when an update event is received for any of my windows. It
- calls the appropriate window’s update routine to draw its contents. As an
- efficiency measure that does not have to be followed, it calls the drawing
- routine only if the visRgn is non-empty. This will handle situations
- where calculations for drawing or drawing itself is very time-consuming.
- Why does QD give you an update with an empty updateRgn?}
-
- BEGIN
- BeginUpdate(window); {setup the visRgn, clears updateRgn}
- IF NOT EmptyRgn(window^.visRgn) THEN BEGIN {if updating to be done}
- SetPort(window); {set to the current port}
- CASE GetWRefCon(window) OF {call the window’s drawing routine}
-
- rSoundWindow:
- DrawSndWindow(window);
-
- rStatusWindow:
- DrawStatusWindow;
-
- rAboutWindow:
- DrawAboutWindow(window);
- END;
- END;
- EndUpdate(window); {restores the visRgn}
- END; {DoUpdate}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoActivate(window: WindowPtr; becomingActive: BOOLEAN);
-
- {This is called when a window is to be activated or deactivated. For the
- document window I activate all the controls and the list. For the status
- window I activate the one and only control. This is also called for
- suspend and resume events while running MultiFinder.}
-
- VAR
- kind: INTEGER;
- button: ControlHandle;
- r: Rect;
-
- BEGIN
- IF window <> NIL THEN BEGIN
- CASE GetWRefCon(window) OF
-
- rSoundWindow: BEGIN
- ActivateSndCntls(SndDocPeek(window));
- LActivate(becomingActive, SndDocPeek(window)^.list);
- END;
-
- rStatusWindow: BEGIN
- IF becomingActive THEN {it only has one control}
- HiliteControl(WindowPeek(window)^.controlList, kCntlActivate)
- ELSE
- HiliteControl(WindowPeek(window)^.controlList, kCntlDeactivate);
- DoButtonOutline(WindowPeek(window)^.controlList);
- END;
-
- rAboutWindow: BEGIN
- IF becomingActive THEN {it only has one control}
- HiliteControl(WindowPeek(window)^.controlList, kCntlActivate)
- ELSE
- HiliteControl(WindowPeek(window)^.controlList, kCntlDeactivate);
- DoButtonOutline(WindowPeek(window)^.controlList);
- END;
-
- END;
- END;
- END; {DoActivate}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE DoEvent(event: EventRecord);
-
- {As Spike Lee says, “Do the right thing.” Determine what kind of event it
- is, and call the appropriate routines. Key down events are first tested
- as being command key events for menus or command-. for cancel. If it’s
- the command-. the user is asking to cancel the sound. In this case, if
- the front window is the status window then the button should look as if it
- has been clicked. This is proper human interface. Any non-menu command
- keys are passed to DoKeyDown. I have a global flag, gInModalState, which
- is used to handle the front window as a modal dialog. This means I ignore
- clicks outside of the modal window and menu command keys. I could have a
- different global flag that would mean it’s a modal window but uses menu
- commands, such as gInModalMenuState. An important note is that I do pass
- any other non-menu command keys to DoKeys, so the modal window could have
- access to key events. One last thing to note happens during a suspend
- event. Applications using sound should dispose of their sound channels
- at suspend time. No other application can use sound while another one has
- channels allocated. To stop my sound, I call KillSound which hides the
- status window. MultiFinder does properly deactivate the front window at
- suspend time and applications normally do not have to worry about
- HiliteWindow. I do because the front window could have been the status
- window and I’ve removed it after getting a suspend event. This causes the
- next window, a document window, to become highlighted as the active
- window. To avoid my next window from being highlighted while in the
- background, I have to call HiliteWindow *after* KillSound. This is
- strange and only applications changing the front window at suspend event
- time have to be concerned about this.
-
- VERSION 1.1: Added a constant, kSFTopLeft, to specify the dialog position.
- I do not call KillSound during a MultiFinder switch unless we're running
- the older Sound Manager. The new one allows for multiple sound
- channels or will return the proper error otherwise.}
-
- CONST
- {OSEvent is the event number of the suspend/resume and mouse-moved events
- sent by MultiFinder. Once I determine an osEvent has occurred, I look at
- the high byte of the message sent to determine which kind it is. To
- further differentiate suspend and resume events, it is necessary to check
- the low bit of the message. I’m using the more efficient MPW bit
- routines.}
-
- { osEvt = app4Evt; defined in post MPW 3.0 Events.p}
- { suspendResumeMessage = 1; defined in post MPW 3.0 Events.p}
- kGetHighByte = 24; {24 bits (three bytes) to shift right}
- kResumeMask = 1; {resume event is a 1 in low bit of message}
-
- VAR
- window: WindowPtr;
- finalTicks: LongInt;
- part, err: INTEGER;
- ignore: BOOLEAN;
- key: CHAR;
-
- BEGIN
- CASE event.what OF
- mouseDown: BEGIN
- part:= FindWindow(event.where, window);
- IF (IsModalWindow(FrontWindow) & (window <> FrontWindow))
- | (IsModalWindow(FrontWindow) & (part = inMenuBar)) THEN BEGIN
- DoErrorSound(1); {click outside of modal window}
- EXIT(DoEvent); {break out of routine}
- END;
- CASE part OF
- inMenuBar: BEGIN {process the menu command}
- AdjustMenus;
- DoMenuCommand(MenuSelect(event.where));
- END;
-
- inSysWindow: {let system handle the mouseDown}
- SystemClick(event, window);
-
- inContent: BEGIN
- IF (window <> FrontWindow) THEN
- SelectWindow(window)
- ELSE BEGIN
- CASE GetWRefCon(window) OF
-
- rSoundWindow:
- DoSndDocClick(SndDocPeek(window), event);
-
- rStatusWindow:
- DoStatClick(StatWindowPeek(window), event);
-
- rAboutWindow:
- DoAboutClick(window, event);
-
- END; {CASE GetWRefCon(window)}
- END;
- END; {inContent}
-
- inDrag: {pass qd.screenBits.bounds to get all gDevices}
- DragWindow(window, event.where, qd.screenBits.bounds);
-
- inGoAway: BEGIN
- IF TrackGoAway(window, event.where) THEN
- ignore:= DoCloseWindow(window);
- END;
-
- END; {CASE}
- END; {mouseDown}
-
- keyDown, autoKey: BEGIN
- window:= FrontWindow;
- key:= CHR(BAnd(event.message, charCodeMask));
- IF BAnd(event.modifiers, cmdKey) <> 0 THEN BEGIN {Command key down}
- IF key = kPeriod THEN BEGIN
- IF window = WindowPtr(gStatusWindow) THEN
- SelectButton(WindowPeek(gStatusWindow)^.controlList);
- KillSound;
- END ELSE
- IF (event.what = keyDown) & (NOT IsModalWindow(window)) THEN BEGIN
- AdjustMenus; {enable/disable/check menu items properly}
- DoMenuCommand(MenuKey(key));
- END;
- END ELSE {non-Command keys}
- IF window <> NIL THEN {if there’s a window}
- DoKeyDown(key, window);
- END;
-
- activateEvt: {TRUE for activate, FALSE for deactivate}
- DoActivate(WindowPtr(event.message),
- BAnd(event.modifiers, activeFlag) <> 0);
-
- updateEvt: {call DoUpdate with the window to update}
- DoUpdate(WindowPtr(event.message));
-
- diskEvt: {Call DIBadMount in response to a diskEvt}
- IF HiWord(event.message) <> noErr THEN BEGIN
- err:= DIBadMount(Point(kSFTopLeft), event.message);
- END;
-
- osEvt: BEGIN
- CASE BSR(event.message, kGetHighByte) OF {high byte of message}
-
- suspendResumeMessage: BEGIN
- IF BAnd(event.message, kResumeMask) <> 0 THEN
- gInBackground:= FALSE {it was a resume event}
- ELSE BEGIN
- gInBackground:= TRUE; {it was a suspend event}
- IF NOT HasNewSndMgr THEN
- KillSound; {stop any sound}
- window:= FrontWindow; {get front window}
- IF window <> NIL THEN {don’t use a NIL window}
- HiliteWindow(window, FALSE); {then properly activate it}
- END;
- DoActivate(FrontWindow, NOT gInBackground);
- END; {suspendResumeMessage}
-
- END; {CASE}
- END; {osEvt}
-
- END; {CASE event.what}
- END; {DoEvent}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE AdjustCursor(region: RgnHandle);
-
- {Change the cursor’s shape, depending on its current position. This also
- calculates the region where the current cursor resides (for
- WaitNextEvent). This is based on its current position in global
- coordinates. If the mouse is ever outside of that region, an event is
- generated causing this routine to be called again by the event loop. This
- allows me to change the region to where the mouse is currently located.
- If there is more to the event than just “the mouse moved,” this gets
- called before the event is processed to make sure the cursor is the right
- one. In any (ahem) event, this is called again before I fall back into
- WaitNextEvent. Extreme integer values are used to create a wide open
- region. -INT_MAX - 1 is the largest negative integer (-32768) and INT_MAX
- is the largest positive integer (32767).
-
- BUG NOTE: The largest positive value for a region’s size is INT_MAX - 1
- due to a very old bug that still remains to this day.}
-
- VAR
- window: WindowPtr;
- arrowRgn,
- sndCursorRgn: RgnHandle;
- sndCursorRect: Rect;
-
- BEGIN
- window:= FrontWindow; {I only adjust the cursor when I am in front}
- IF (NOT gInBackground) & (NOT IsDAWindow(window)) THEN BEGIN
-
- arrowRgn:= NewRgn; {calculate regions for different cursor shapes}
- sndCursorRgn:= NewRgn; {start with a big, big rectangular region}
- SetRectRgn(arrowRgn, -MAXINT - 1, -MAXINT - 1, MAXINT - 1, MAXINT - 1);
-
- IF IsDocWindow(window) THEN BEGIN {calculate region for document cursor}
- sndCursorRect:= window^.portRect;
- SetPort(window);
- WITH sndCursorRect DO BEGIN {make a global version of the viewRect}
- LocalToGlobal(topLeft);
- LocalToGlobal(botRight);
- END;
- RectRgn(sndCursorRgn, sndCursorRect);
- WITH window^.portBits.bounds DO
- SetOrigin(-left, -top);
- SectRgn(sndCursorRgn, window^.visRgn, sndCursorRgn);
- SetOrigin(0, 0);
- END;
-
- DiffRgn(arrowRgn, sndCursorRgn, arrowRgn); {subtract other region}
- IF PtInRgn(GetGlobalMouse, sndCursorRgn) THEN BEGIN
- SetCursor(GetCursor(rSndCursor)^^); {change cursor and region}
- CopyRgn(sndCursorRgn, region);
- END ELSE BEGIN
- SetCursor(qd.arrow);
- CopyRgn(arrowRgn, region);
- END;
- DisposeRgn(arrowRgn); {get rid of our local regions}
- DisposeRgn(sndCursorRgn);
- END;
- END; {AdjustCursor}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- PROCEDURE EventLoop;
-
- {Get events forever, and handle them by calling DoEvent. Since this
- application requires System 6.0x or later I know that _WaitNextEvent is
- always there, even without MultiFinder. MultiFinder’s sleep is used to
- determine how often I want to receive events, regardless if an event has
- actually occurred. In this application, I don’t perform any background
- processing so I’m being super friendly by using MAXLONGINT as a sleep
- value. This also helps keep Virtual Memory from paging me in just to run
- my event loop and find out that nothing happened. The application will
- only be called upon for events that must be handled. Also call
- AdjustCursor each time through the loop. AdjustCursor will return the
- current region containing the mouse and is passed to WaitNextEvent. If
- the mouse travels out of the region, another event is generated. I have
- to call AdjustCursor just before doing the event to make sure the right
- cursor is shown. Another thing, if I have a sound playing asynchronously
- then I want to dispose of my channel as soon as possible. I set up a flag
- in the SoundUnit that will keep track of when it has a channel allocated.
- I can call the SndChanOpen function to find out if this is true. It
- pretty much like keeping track of when you’re in the background. If a
- channel is open, then the MultiFinder sleep time is adjusted to a
- reasonable time that will allow me to catch when the sound has completed
- so that I may dispose of my channels and status window. That’s when I
- return kPollingSleepTime.
-
- VERSION 1.1: No longer putting the SANELib into a seperate unit, which then
- needed to be unloaded. Instead, I merge it into the Main segment. Refer
- to the Make file for further information.}
-
-
- VAR
- cursorRgn: RgnHandle;
- event: EventRecord;
- sleep: LONGINT;
-
- BEGIN
- cursorRgn:= NewRgn; {1st time pass WNE an empty region}
- REPEAT
- IF SndChanOpen THEN {if we’re playing a sound}
- sleep:= kPollingSleepTime {use the polling sleep value}
- ELSE BEGIN
- sleep:= MAXLONGINT; {default value for sleep}
- UnloadSeg(@_SoundUnit); {unload the Sound Unit}
- END;
- UnloadSeg(@OpenSoundDoc); {unload the open code}
- IF SoundCompletion THEN
- KillSound;
- IF LowOnReserve THEN
- RecoverReserve;
- AdjustCursor(cursorRgn); {get the right cursor}
- IF WaitNextEvent(everyEvent, event, sleep, cursorRgn) THEN BEGIN
- AdjustCursor(cursorRgn); {get the right cursor}
- DoEvent(event);
- END;
- UNTIL FALSE; {loop forever; I quit through Terminate}
- END; {EventLoop}
-
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Initialization}
- PROCEDURE Initialize;
-
- {Set up the whole world. Initialize global variables, Toolbox managers,
- and menus. If a failure occurs here, I will consider that the application
- is in such bad shape that I should just exit. Your error handling may
- differ, but the checks should still be made. I ask for a set of master
- pointer blocks and all permanent storage at this point to cut down on
- memory fragmentation. I may be opening large numbers of resources and
- documents so I allocate some extra master pointer blocks at the start.
-
- VERSION 1.1: Now supports opening of documents from the Finder.}
-
- CONST
- kBroughtToFront = 3;
-
- VAR
- event: EventRecord;
- menuBar: Handle;
- total, contig: LONGINT;
- ignoreError: OSErr;
- ignoreResult: BOOLEAN;
- theFile: AppFile;
- message, count,
- index: INTEGER;
-
- BEGIN
- gInBackground:= FALSE; {we’ll be in the foreground soon}
- gAppResRef:= CurResFile; {save the resRef to myself}
- FOR count:= 1 TO kNumberOfMasters DO {allocate master pointer blocks}
- MoreMasters;
- InitGraf(@qd.thePort); {init managers, yawn...}
- InitFonts;
- InitWindows;
- InitMenus;
- TEInit;
- InitDialogs(NIL);
- InitCursor;
-
- {This next line prevents the Dialog Manager from calling _SysBeep.
- If I get a memory or resource Manager error it wouldn’t be the best
- of plans to call _SysBeep which will want to allocate memory and load
- a few resources. Which could be bad if I just gave an Alert signalling
- an out of memory condition.}
-
- ErrorSound(@DoErrorSound);
-
- {This code is necessary to pull the application into the foreground. I use
- EventAvail because I don’t want to remove any events the user may have
- done, such as typing ahead. Until the application has made a few calls (3
- seems to be the magic number) to the Event Manager, MultiFinder keeps me
- in the background. Splashscreens and Alerts will remain in a background
- layer until we get a few events. This is documented in Tech Note #180.}
-
- FOR count:= 1 TO kBroughtToFront DO
- ignoreResult:= EventAvail(everyEvent, event);
-
- {Ignore the error returned from SysEnvirons; even if an error occurred,
- the SysEnvirons glue will fill in the SysEnvRec. You can save a redundant
- call to SysEnvirons by calling it after initializing AppleTalk.}
-
- ignoreError:= SysEnvirons(kSysEnvironsVersion, gMac);
-
- {Make sure we’re running the right system. If the Sound Manager is not
- available, then there’s no point in continuing.}
-
- IF gMac.systemVersion < kSystem602 THEN
- EmergencyExit(sWrongVersion);
-
- {Call the SoundUnit to initialize itslef. If the unit encounters an error,
- then it cannot be used and this means I’m leaving too.}
-
- IF InitSoundUnit <> noErr THEN {allocates 4 * 1064 bytes}
- EmergencyExit(sInitSoundErr);
-
- {Before I go any further, I want my reserve memory. This is an emergency
- reserve (sorta like my old VW had) when memory runs low. If I cannot
- obtain this reserve, then I’ll bail. It’s also important to obtain my
- reserve before testing if I have the desired amount of memory to run
- this application. Also, FailLowMemory will consider the memory reserve.}
-
- IF NOT AllocateReserve THEN
- EmergencyExit(sLowMemory);
- SetGrowZone(@MyGrowZone);
-
- menuBar:= GetNewMBar(rMenuBar); {read menus into menu bar}
- IF menuBar = NIL THEN
- EmergencyExit(sNoMenus); {wow, how’d that happen?}
- SetMenuBar(menuBar); {install menus}
- DisposeHandle(menuBar);
- AppendResMenu(GetMenuHandle(mApple), 'DRVR'); {add DA names to Apple menu}
- DrawMenuBar;
-
- InitStatusWindow; {get the status window ready}
- InitCursorCtl(NIL); {MPW’s handy cursor routines}
-
- {Last, I want to make sure that enough memory is free for my application to
- run. It is possible that user may have adjusted the SIZE resource to too
- small a setting or for some other reason the application started up in a
- very small memory partition. It’s also possible for a situation to arise
- where the heap may have been of the requested size taken from the SIZE
- resource, but a large scrap was loaded which left too little memory. I
- want to make sure that my free memory is not being modified by the scrap’s
- presence. So, I unload it to disk but if the application will run once
- the scrap is unloaded, then you’ll probably not get it back into memory.
- Thus losing the clipboard contents. I preform this check after
- initializing all the Toolbox and the basic features of this application,
- such as showing the about box.}
-
- IF FailLowMemory(kMinSpace) THEN BEGIN
- IF UnloadScrap <> noErr THEN
- EmergencyExit(sLowMemory)
- ELSE BEGIN
- IF FailLowMemory(kMinSpace) THEN
- EmergencyExit(sLowMemory);
- END;
- END;
-
- {I check the sound volume after testing for a minimal amount of memory to
- run. Otherwise I might end up telling the user to adjust the volume and
- the quitting because memory was too low.}
-
- CheckSndVol; {check the sound volume level}
-
- CountAppFiles(message, count); {any files to open?}
- FOR index:= 1 TO count DO BEGIN {go get 'em}
- GetAppFiles(index, theFile);
- OpenSoundDoc(@theFile.fName, theFile.vRefNum);
- END;
- ClrAppFiles(count);
- END; {Initialize}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- PROCEDURE _DataInit; EXTERNAL;
-
- {This routine is contained in the MPW runtime library. It will be placed
- into the code segment used to initialize the A5 globals. This external
- reference to it is done so that we can unload that segment, named %A5Init.}
-
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN PROGRAM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {$S Main}
- BEGIN
- UnloadSeg(@_DataInit); {note that _DataInit must not be in Main!}
-
- {If you have stack requirements that differ from the default, then you
- could use SetApplLimit to increase StackSpace at this point, before
- calling MaxApplZone.}
-
- MaxApplZone; {expand the heap so code segments load at the top}
- Initialize; {initialize the program}
- UnloadSeg(@Initialize); {note that Initialize must not be in Main!}
- EventLoop; {call the main event loop}
- END.
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN PROGRAM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
- {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
-